mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-3883 added source file manager
This commit is contained in:
parent
420dd7ce0c
commit
9aeeaa4397
52 changed files with 8432 additions and 306 deletions
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.util.BigEndianDataConverter;
|
||||
|
||||
/**
|
||||
* A SourceFile is an immutable object representing a source file. It contains an
|
||||
* absolute path along with an optional {@link SourceFileIdType} and identifier.
|
||||
* For example, if the id type is {@link SourceFileIdType#MD5}, the identifier would
|
||||
* be the md5 sum of the source file (stored as a byte array).
|
||||
* <p>
|
||||
* Note: path parameters are assumed to be absolute file paths with forward slashes as the
|
||||
* separator. For other cases, e.g. windows paths, consider the static convenience methods in
|
||||
* the {@code SourceFileUtils} class.
|
||||
* <p>
|
||||
* Note: you can use {@code SourceFileUtils.hexStringToByteArray} to convert hex Strings to byte
|
||||
* arrays. You can use {@code SourceFileUtils.longToByteArray} to convert long values to the
|
||||
* appropriate byte arrays.
|
||||
*/
|
||||
public final class SourceFile implements Comparable<SourceFile> {
|
||||
|
||||
private static final String FILE_SCHEME = "file";
|
||||
private static HexFormat hexFormat = HexFormat.of();
|
||||
private final String path;
|
||||
private final String filename;
|
||||
private final SourceFileIdType idType;
|
||||
private final byte[] identifier;
|
||||
private final int hash;
|
||||
private final String idDisplayString;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor requiring only a path. The path will be normalized (see {@link URI#normalize})
|
||||
* The id type will be set to {@code SourceFileIdType.NONE} and the identifier will
|
||||
* be set to an array of length 0.
|
||||
*
|
||||
* @param path path
|
||||
*/
|
||||
public SourceFile(String path) {
|
||||
this(path, SourceFileIdType.NONE, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. The path will be normalized (see {@link URI#normalize}).
|
||||
* <p>
|
||||
* Note: if {@code type} is {@code SourceFileIdType.NONE}, the {@code identifier}
|
||||
* parameter is ignored.
|
||||
* <p>
|
||||
* Note: use {@code SourceFileUtils.longToByteArray} to convert a {@code long} value
|
||||
* to the appropriate {@code byte} array.
|
||||
* @param path path
|
||||
* @param type id type
|
||||
* @param identifier id
|
||||
*/
|
||||
public SourceFile(String path, SourceFileIdType type, byte[] identifier) {
|
||||
this(path, type, identifier, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. The path will be normalized (see {@link URI#normalize}).
|
||||
* <p>
|
||||
* Note: if {@code type} is {@code SourceFileIdType.NONE}, the {@code identifier}
|
||||
* parameter is ignored.
|
||||
* <p>
|
||||
* IMPORTANT: only pass {@code false} as {@code validate} parameter if you are certain that
|
||||
* validation can be skipped, e.g., you are creating a SourceFile from information
|
||||
* read out of the database which was validated at insertion.
|
||||
* @param pathToValidate path
|
||||
* @param type sourcefile id type
|
||||
* @param identifier identifier
|
||||
* @param validate true if params should be validated
|
||||
*/
|
||||
SourceFile(String pathToValidate, SourceFileIdType type, byte[] identifier, boolean validate) {
|
||||
if (validate) {
|
||||
if (StringUtils.isBlank(pathToValidate)) {
|
||||
throw new IllegalArgumentException("pathToValidate cannot be null or blank");
|
||||
}
|
||||
try {
|
||||
URI uri = new URI(FILE_SCHEME, null, pathToValidate, null).normalize();
|
||||
path = uri.getPath();
|
||||
if (path.endsWith("/")) {
|
||||
throw new IllegalArgumentException(
|
||||
"SourceFile URI must represent a file (not a directory)");
|
||||
}
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("path not valid: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
path = pathToValidate;
|
||||
}
|
||||
this.idType = type;
|
||||
filename = path.substring(path.lastIndexOf("/") + 1);
|
||||
this.identifier = validateAndCopyIdentifier(identifier);
|
||||
hash = computeHashcode();
|
||||
idDisplayString = computeIdDisplayString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file URI for this SourceFile.
|
||||
* @return uri
|
||||
*/
|
||||
public URI getUri() {
|
||||
try {
|
||||
return new URI(FILE_SCHEME, null, path, null);
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new AssertionError("URISyntaxException on validated path");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path
|
||||
* @return path
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename
|
||||
* @return filename
|
||||
*/
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source file identifier type
|
||||
* @return id type
|
||||
*/
|
||||
public SourceFileIdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns (a copy of) the identifier
|
||||
* @return identifier
|
||||
*/
|
||||
public byte[] getIdentifier() {
|
||||
byte[] copy = new byte[identifier.length];
|
||||
System.arraycopy(identifier, 0, copy, 0, identifier.length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SourceFile otherFile)) {
|
||||
return false;
|
||||
}
|
||||
if (!path.equals(otherFile.path)) {
|
||||
return false;
|
||||
}
|
||||
if (!idType.equals(otherFile.idType)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.equals(identifier, otherFile.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(path);
|
||||
if (idType.equals(SourceFileIdType.NONE)) {
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append(" [");
|
||||
sb.append(idType.name());
|
||||
sb.append("=");
|
||||
sb.append(getIdAsString());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SourceFile sourceFile) {
|
||||
int comp = path.compareTo(sourceFile.path);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
comp = idType.compareTo(sourceFile.idType);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
return Arrays.compare(identifier, sourceFile.identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String representation of the identifier
|
||||
* @return id display string
|
||||
*/
|
||||
public String getIdAsString() {
|
||||
return idDisplayString;
|
||||
}
|
||||
|
||||
// immutable object - compute hash and cache
|
||||
private int computeHashcode() {
|
||||
int result = path.hashCode();
|
||||
result = 31 * result + idType.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(identifier);
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] validateAndCopyIdentifier(byte[] array) {
|
||||
if (array == null || idType == SourceFileIdType.NONE) {
|
||||
array = new byte[0];
|
||||
}
|
||||
if (array.length > SourceFileIdType.MAX_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"identifier array too long; max is " + SourceFileIdType.MAX_LENGTH);
|
||||
}
|
||||
if (idType.getByteLength() != 0 && idType.getByteLength() != array.length) {
|
||||
throw new IllegalArgumentException(
|
||||
"identifier array has wrong length for " + idType.name());
|
||||
}
|
||||
byte[] copy = new byte[array.length];
|
||||
System.arraycopy(array, 0, copy, 0, array.length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private String computeIdDisplayString() {
|
||||
switch (idType) {
|
||||
case NONE:
|
||||
return StringUtils.EMPTY;
|
||||
case TIMESTAMP_64:
|
||||
return Instant.ofEpochMilli(BigEndianDataConverter.INSTANCE.getLong(identifier))
|
||||
.toString();
|
||||
default:
|
||||
return hexFormat.formatHex(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Base class for adapters to access the Source File table. The table has one column, which stores
|
||||
* the path of the source file (as a String).
|
||||
*/
|
||||
abstract class SourceFileAdapter {
|
||||
|
||||
static final String TABLE_NAME = "SourceFiles";
|
||||
static final int PATH_COL = SourceFileAdapterV0.V0_PATH_COL;
|
||||
static final int ID_TYPE_COL = SourceFileAdapterV0.V0_ID_TYPE_COL;
|
||||
static final int ID_COL = SourceFileAdapterV0.V0_ID_COL;
|
||||
|
||||
/**
|
||||
* Creates an adapter for the source file table.
|
||||
* @param handle database handle
|
||||
* @param openMode open mode
|
||||
* @param monitor task monitor
|
||||
* @return adapter for table
|
||||
* @throws VersionException if version incompatible
|
||||
*/
|
||||
static SourceFileAdapter getAdapter(DBHandle handle, OpenMode openMode, TaskMonitor monitor)
|
||||
throws VersionException {
|
||||
return new SourceFileAdapterV0(handle, openMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RecordIterator} for this table.
|
||||
* @return record iterator
|
||||
* @throws IOException on db error
|
||||
*/
|
||||
abstract RecordIterator getRecords() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the {@link DBRecord} corresponding to {@code sourceFile}, or {@code null} if
|
||||
* no such record exists.
|
||||
* @param sourceFile source file
|
||||
* @return record or null
|
||||
* @throws IOException on db error
|
||||
*/
|
||||
abstract DBRecord getRecord(SourceFile sourceFile) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the {@link DBRecord} with key {@code id}, or {@code null} if no such record exists.
|
||||
* @param id id
|
||||
* @return record or null
|
||||
* @throws IOException on db error
|
||||
*/
|
||||
abstract DBRecord getRecord(long id) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a {@link DBRecord} for {@link SourceFile} {@code sourceFile}. If a record for
|
||||
* that source file already exists, the existing record is returned.
|
||||
* @param sourceFile source file
|
||||
* @return db record
|
||||
* @throws IOException on db error
|
||||
*/
|
||||
abstract DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException;
|
||||
|
||||
/**
|
||||
* Deletes the record with id {@code id} from the database.
|
||||
* @param id id to delete
|
||||
* @return true if deleted successfully
|
||||
* @throws IOException on database error
|
||||
*/
|
||||
abstract boolean removeSourceFileRecord(long id) throws IOException;
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import db.*;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.program.database.util.EmptyRecordIterator;
|
||||
import ghidra.util.exception.VersionException;
|
||||
|
||||
/**
|
||||
* Initial version of {@link SourceFileAdapter}.
|
||||
*/
|
||||
class SourceFileAdapterV0 extends SourceFileAdapter implements DBListener {
|
||||
|
||||
final static int SCHEMA_VERSION = 0;
|
||||
static final int V0_PATH_COL = 0;
|
||||
static final int V0_ID_TYPE_COL = 1;
|
||||
static final int V0_ID_COL = 2;
|
||||
|
||||
private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
|
||||
new Field[] { StringField.INSTANCE, ByteField.INSTANCE, BinaryField.INSTANCE },
|
||||
new String[] { "Path", "IdType", "Identifier" }, new int[] { V0_PATH_COL });
|
||||
|
||||
private Table table; // lazy creation, null if empty
|
||||
private final DBHandle dbHandle;
|
||||
private static final int[] INDEXED_COLUMNS = new int[] { V0_PATH_COL };
|
||||
|
||||
SourceFileAdapterV0(DBHandle dbHandle, OpenMode openMode) throws VersionException {
|
||||
this.dbHandle = dbHandle;
|
||||
|
||||
// As in FunctionTagAdapterV0, we need to add this as a database listener.
|
||||
// Since the table is created lazily, undoing a transaction which (for example) caused
|
||||
// the table to be created can leave the table in a bad state.
|
||||
// The implementation of dbRestored(DBHandle) solves this issue.
|
||||
this.dbHandle.addListener(this);
|
||||
|
||||
if (!openMode.equals(OpenMode.CREATE)) {
|
||||
table = dbHandle.getTable(TABLE_NAME);
|
||||
if (table == null) {
|
||||
return; // perform lazy table creation
|
||||
}
|
||||
int version = table.getSchema().getVersion();
|
||||
if (version != SCHEMA_VERSION) {
|
||||
throw new VersionException(VersionException.NEWER_VERSION, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbRestored(DBHandle dbh) {
|
||||
table = dbh.getTable(TABLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbClosed(DBHandle dbh) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tableDeleted(DBHandle dbh, Table deletedTable) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tableAdded(DBHandle dbh, Table addedTable) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
RecordIterator getRecords() throws IOException {
|
||||
if (table == null) {
|
||||
return new EmptyRecordIterator();
|
||||
}
|
||||
return table.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
DBRecord getRecord(long id) throws IOException {
|
||||
if (table == null) {
|
||||
return null;
|
||||
}
|
||||
return table.getRecord(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
DBRecord getRecord(SourceFile sourceFile) throws IOException {
|
||||
if (table == null) {
|
||||
return null;
|
||||
}
|
||||
StringField field = new StringField(sourceFile.getPath());
|
||||
RecordIterator iter = table.indexIterator(V0_PATH_COL, field, field, true);
|
||||
while (iter.hasNext()) {
|
||||
DBRecord rec = iter.next();
|
||||
if (rec.getByteValue(V0_ID_TYPE_COL) != sourceFile.getIdType().getIndex()) {
|
||||
continue;
|
||||
}
|
||||
if (Arrays.equals(sourceFile.getIdentifier(), rec.getBinaryData(V0_ID_COL))) {
|
||||
return rec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException {
|
||||
DBRecord rec = getRecord(sourceFile);
|
||||
if (rec == null) {
|
||||
rec = V0_SCHEMA.createRecord(getTable().getKey());
|
||||
rec.setString(V0_PATH_COL, sourceFile.getPath());
|
||||
rec.setByteValue(V0_ID_TYPE_COL, sourceFile.getIdType().getIndex());
|
||||
rec.setBinaryData(V0_ID_COL, sourceFile.getIdentifier());
|
||||
getTable().putRecord(rec);
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean removeSourceFileRecord(long id) throws IOException {
|
||||
if (table != null) {
|
||||
return table.deleteRecord(id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Table getTable() throws IOException {
|
||||
if (table == null) {
|
||||
table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An enum whose values represent source file id types, such as md5 or sha1.
|
||||
*/
|
||||
public enum SourceFileIdType {
|
||||
NONE((byte) 0, 0),
|
||||
UNKNOWN((byte) 1, 0),
|
||||
TIMESTAMP_64((byte) 2, 8),
|
||||
MD5((byte) 3, 16),
|
||||
SHA1((byte) 4, 20),
|
||||
SHA256((byte) 5, 32),
|
||||
SHA512((byte) 6, 64);
|
||||
|
||||
public static final int MAX_LENGTH = 64;
|
||||
private static Map<Byte, SourceFileIdType> valueToEnum;
|
||||
|
||||
static {
|
||||
valueToEnum = new HashMap<>();
|
||||
for (SourceFileIdType type : SourceFileIdType.values()) {
|
||||
valueToEnum.put(type.getIndex(), type);
|
||||
}
|
||||
}
|
||||
|
||||
private final int byteLength;
|
||||
private final byte index;
|
||||
|
||||
private SourceFileIdType(byte index, int length) {
|
||||
byteLength = length;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte length of the corresponding identifier. A value of 0 indicates
|
||||
* no restriction.
|
||||
*
|
||||
* @return byte length of identifier
|
||||
*/
|
||||
public int getByteLength() {
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the identifier type.
|
||||
* @return index
|
||||
*/
|
||||
byte getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id type given the index.
|
||||
* @param index index
|
||||
* @return id type
|
||||
*/
|
||||
static SourceFileIdType getTypeFromIndex(byte index) {
|
||||
return valueToEnum.get(index);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,857 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import db.*;
|
||||
import db.util.ErrorHandler;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.ManagerDB;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.map.AddressMapDB;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.sourcemap.*;
|
||||
import ghidra.program.util.ProgramEvent;
|
||||
import ghidra.util.Lock;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Database Manager for managing source files and source map information.
|
||||
*/
|
||||
public class SourceFileManagerDB implements SourceFileManager, ManagerDB, ErrorHandler {
|
||||
|
||||
private ProgramDB program;
|
||||
|
||||
private SourceFileAdapter sourceFileTableAdapter;
|
||||
private SourceMapAdapter sourceMapTableAdapter;
|
||||
private AddressMapDB addrMap;
|
||||
|
||||
protected final Lock lock;
|
||||
|
||||
private Long lastKey;
|
||||
private SourceFile lastSourceFile;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param dbh database handle
|
||||
* @param addrMap map longs to addresses
|
||||
* @param openMode mode
|
||||
* @param lock program synchronization lock
|
||||
* @param monitor task monitor
|
||||
* @throws VersionException if the database is incompatible with the current schema
|
||||
*/
|
||||
public SourceFileManagerDB(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode, Lock lock,
|
||||
TaskMonitor monitor) throws VersionException {
|
||||
this.addrMap = addrMap;
|
||||
sourceFileTableAdapter = SourceFileAdapter.getAdapter(dbh, openMode, monitor);
|
||||
sourceMapTableAdapter = SourceMapAdapter.getAdapter(dbh, addrMap, openMode, monitor);
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgram(ProgramDB program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void programReady(OpenMode openMode, int currentRevision, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCache(boolean all) throws IOException {
|
||||
lastKey = null;
|
||||
lastSourceFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}<br>
|
||||
* Note: this method will split any source map entries that <b>intersect</b> the address
|
||||
* range but are not entirely contained within it. Parts within the range to delete will
|
||||
* be deleted.
|
||||
* @param start first address in range
|
||||
* @param end last address in range
|
||||
* @param monitor task monitor
|
||||
* @throws CancelledException if {@code monitor} is cancelled
|
||||
*/
|
||||
@Override
|
||||
public void deleteAddressRange(Address start, Address end, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
AddressRange.checkValidRange(start, end);
|
||||
AddressRange rangeToDelete = new AddressRangeImpl(start, end);
|
||||
lock.acquire();
|
||||
try {
|
||||
|
||||
RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(end, false);
|
||||
boolean sourceMapChanged = false;
|
||||
|
||||
// Note: we iterate backwards since records are stored based on the start address.
|
||||
// We need to delete records stored before the beginning of the delete range that
|
||||
// overlap the delete range, but we will never need to delete records that start after
|
||||
// the delete range.
|
||||
List<SourceMapEntryData> entriesToCreate = new ArrayList<>();
|
||||
while (recIter.hasPrevious()) {
|
||||
monitor.checkCancelled();
|
||||
DBRecord rec = recIter.previous();
|
||||
Address recStart = getStartAddress(rec);
|
||||
long recLength = getLength(rec);
|
||||
|
||||
if (recLength == 0) {
|
||||
// if length 0 entry is in range to delete, delete entry
|
||||
// otherwise ignore
|
||||
if (rangeToDelete.contains(recStart)) {
|
||||
recIter.delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Address recEnd = getEndAddress(recStart,recLength);
|
||||
|
||||
if (!rangeToDelete.intersects(recStart, recEnd)) {
|
||||
// we've found an entry that does not touch the range to delete
|
||||
// this means we've handled all relevant entries and can stop
|
||||
break;
|
||||
}
|
||||
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
long fileId = fileAndLine >> 32;
|
||||
int lineNum = (int) (fileAndLine & 0xffffffff);
|
||||
if (isLessInSameSpace(recStart, start)) {
|
||||
// rangeToDelete intersects (recStart,recEnd), so
|
||||
// the entry must overlap the left endpoint of rangeToDelete
|
||||
long length = start.subtract(recStart);
|
||||
SourceMapEntryData data =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, length);
|
||||
entriesToCreate.add(data);
|
||||
}
|
||||
|
||||
if (isLessInSameSpace(end, recEnd)) {
|
||||
// rangeToDelete intersects (recStart,recEnd)
|
||||
// entry must overlap right endpoint of rangeToDelete
|
||||
long length = recEnd.subtract(end);
|
||||
SourceMapEntryData data =
|
||||
new SourceMapEntryData(fileId, lineNum, end.add(1), length);
|
||||
entriesToCreate.add(data);
|
||||
}
|
||||
recIter.delete();
|
||||
}
|
||||
// add the new entries
|
||||
for (SourceMapEntryData data : entriesToCreate) {
|
||||
sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
|
||||
data.baseAddress, data.length);
|
||||
}
|
||||
if (sourceMapChanged) {
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}<br>
|
||||
* Note: this method will move any source map entry which is entirely contained within the
|
||||
* source address range. Entries which also contain addresses outside of this range will
|
||||
* be split and parts within the range to move will be moved.
|
||||
* @param fromAddr first address of range to be moved
|
||||
* @param toAddr target address
|
||||
* @param length number of addresses to move
|
||||
* @param monitor task monitor
|
||||
* @throws AddressOverflowException if overflow occurs when computing new addresses
|
||||
* @throws CancelledException if {@code monitor} is cancelled
|
||||
*/
|
||||
@Override
|
||||
public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
|
||||
throws AddressOverflowException, CancelledException {
|
||||
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid negative length for moveAddressRange: " + length);
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
lock.acquire();
|
||||
|
||||
try {
|
||||
Address rangeToMoveEnd = fromAddr.addNoWrap(length - 1);
|
||||
AddressRange rangeToMove = new AddressRangeImpl(fromAddr, rangeToMoveEnd);
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(rangeToMoveEnd, false);
|
||||
boolean mapChanged = false;
|
||||
|
||||
// Note: we iterate backwards since records are stored based on the start address.
|
||||
// We need to move records stored before the beginning of rangeToMove that
|
||||
// overlap rangeToMove but we will never need to move records that start after
|
||||
// rangeToMove.
|
||||
List<SourceMapEntryData> entriesToCreate = new ArrayList<>();
|
||||
while (recIter.hasPrevious()) {
|
||||
monitor.checkCancelled();
|
||||
DBRecord rec = recIter.previous();
|
||||
long entryLength = getLength(rec);
|
||||
if (entryLength == 0) {
|
||||
continue; // nothing to check
|
||||
}
|
||||
Address recStart = getStartAddress(rec);
|
||||
Address recEnd = getEndAddress(recStart, entryLength);
|
||||
|
||||
if (!rangeToMove.intersects(recStart, recEnd)) {
|
||||
// we've found an entry entirely before rangeToMove
|
||||
// this means we can stop looking
|
||||
break;
|
||||
}
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
long fileId = fileAndLine >> 32;
|
||||
int lineNum = (int) (fileAndLine & 0xffffffff);
|
||||
|
||||
if (isLessInSameSpace(recStart, fromAddr) &&
|
||||
isLessInSameSpace(rangeToMoveEnd, recEnd)) {
|
||||
// entry extends over left and right endpoint of rangeToMove
|
||||
long newLength = fromAddr.subtract(recStart);
|
||||
SourceMapEntryData left =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
|
||||
entriesToCreate.add(left);
|
||||
SourceMapEntryData middle =
|
||||
new SourceMapEntryData(fileId, lineNum, fromAddr, length);
|
||||
entriesToCreate.add(middle);
|
||||
newLength = recEnd.subtract(rangeToMoveEnd);
|
||||
SourceMapEntryData right =
|
||||
new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
|
||||
entriesToCreate.add(right);
|
||||
recIter.delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isLessInSameSpace(recStart, fromAddr)) {
|
||||
// entry extends before left endpoint (but not past right)
|
||||
long newLength = fromAddr.subtract(recStart);
|
||||
SourceMapEntryData left =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
|
||||
entriesToCreate.add(left);
|
||||
newLength = recEnd.subtract(fromAddr) + 1;
|
||||
SourceMapEntryData middle =
|
||||
new SourceMapEntryData(fileId, lineNum, fromAddr, newLength);
|
||||
entriesToCreate.add(middle);
|
||||
recIter.delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isLessInSameSpace(rangeToMoveEnd, recEnd)) {
|
||||
// entry extends past right endpoint (but not before left)
|
||||
long newLength = rangeToMoveEnd.subtract(recStart) + 1;
|
||||
SourceMapEntryData middle =
|
||||
new SourceMapEntryData(fileId, lineNum, recStart, newLength);
|
||||
entriesToCreate.add(middle);
|
||||
newLength = recEnd.subtract(rangeToMoveEnd);
|
||||
SourceMapEntryData right =
|
||||
new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
|
||||
entriesToCreate.add(right);
|
||||
recIter.delete();
|
||||
continue;
|
||||
}
|
||||
// entry is entirely within range to move, no adjustment needed
|
||||
mapChanged = true;
|
||||
|
||||
}
|
||||
|
||||
// add the new entries
|
||||
for (SourceMapEntryData data : entriesToCreate) {
|
||||
sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
|
||||
data.baseAddress, data.length);
|
||||
}
|
||||
|
||||
mapChanged = mapChanged || !entriesToCreate.isEmpty();
|
||||
sourceMapTableAdapter.moveAddressRange(fromAddr, toAddr, length, monitor);
|
||||
|
||||
if (mapChanged) {
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addSourceFile(SourceFile sourceFile) throws LockException {
|
||||
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
|
||||
program.checkExclusiveAccess();
|
||||
lock.acquire();
|
||||
try {
|
||||
Long key = getKeyForSourceFile(sourceFile);
|
||||
if (key != null) {
|
||||
return false;
|
||||
}
|
||||
DBRecord dbRecord = sourceFileTableAdapter.createSourceFileRecord(sourceFile);
|
||||
updateLastSourceFileAndLastKey(dbRecord);
|
||||
program.setObjChanged(ProgramEvent.SOURCE_FILE_ADDED, null, null, sourceFile);
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
|
||||
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
|
||||
boolean mapChanged = false;
|
||||
program.checkExclusiveAccess();
|
||||
lock.acquire();
|
||||
try {
|
||||
Long key = getKeyForSourceFile(sourceFile);
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
|
||||
while (recIter.hasNext()) {
|
||||
recIter.next();
|
||||
recIter.delete();
|
||||
mapChanged = true;
|
||||
}
|
||||
sourceFileTableAdapter.removeSourceFileRecord(key);
|
||||
lastKey = null;
|
||||
lastSourceFile = null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
program.setObjChanged(ProgramEvent.SOURCE_FILE_REMOVED, null, sourceFile, null);
|
||||
if (mapChanged) {
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, sourceFile, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceMapEntry> getSourceMapEntries(Address addr) {
|
||||
|
||||
List<SourceMapEntry> sourceMapEntries = new ArrayList<>();
|
||||
|
||||
lock.acquire();
|
||||
try {
|
||||
RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(addr, false);
|
||||
boolean foundNonZeroLength = false;
|
||||
while (recIter.hasPrevious()) {
|
||||
DBRecord rec = recIter.previous();
|
||||
long entryLength = getLength(rec);
|
||||
Address entryBase = getStartAddress(rec);
|
||||
if (addr.equals(entryBase)) {
|
||||
sourceMapEntries.add(getSourceMapEntry(rec));
|
||||
if (entryLength != 0) {
|
||||
foundNonZeroLength = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (entryLength == 0) {
|
||||
continue; // only want length zero entries if they are based at addr
|
||||
}
|
||||
if (!foundNonZeroLength &&
|
||||
isLessOrEqualInSameSpace(addr, getEndAddress(entryBase, entryLength))) {
|
||||
sourceMapEntries.add(getSourceMapEntry(rec));
|
||||
continue; // continue in case there are additional entries at entryBase
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
Collections.sort(sourceMapEntries);
|
||||
return sourceMapEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
|
||||
long length) throws LockException, AddressOverflowException {
|
||||
if (lineNumber < 0) {
|
||||
throw new IllegalArgumentException("lineNumber cannot be negative");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length cannot be negative");
|
||||
}
|
||||
Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
|
||||
Objects.requireNonNull(baseAddr, "baseAddr cannot be null");
|
||||
MemoryBlock startBlock = program.getMemory().getBlock(baseAddr);
|
||||
if (startBlock == null) {
|
||||
throw new AddressOutOfBoundsException(baseAddr + " is not in a defined memory block");
|
||||
}
|
||||
program.checkExclusiveAccess();
|
||||
|
||||
lock.acquire();
|
||||
try {
|
||||
Long sourceFileId = getKeyForSourceFile(sourceFile);
|
||||
if (sourceFileId == null) {
|
||||
throw new IllegalArgumentException(
|
||||
sourceFile.toString() + " not associated with program");
|
||||
}
|
||||
if (length == 0) {
|
||||
return addZeroLengthEntry(sourceFileId, lineNumber, baseAddr);
|
||||
}
|
||||
Address endAddr = baseAddr.addNoWrap(length - 1);
|
||||
|
||||
// check that the entry's range is entirely contained within defined memory blocks
|
||||
if (!startBlock.contains(endAddr)) {
|
||||
if (!program.getMemory().contains(baseAddr, endAddr)) {
|
||||
throw new AddressOutOfBoundsException(
|
||||
baseAddr + "," + endAddr + " spans undefined memory");
|
||||
}
|
||||
}
|
||||
SourceMapEntryDB entry = null;
|
||||
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(endAddr, false);
|
||||
while (recIter.hasPrevious()) {
|
||||
DBRecord rec = recIter.previous();
|
||||
long entryLength = getLength(rec);
|
||||
if (entryLength == 0) {
|
||||
continue; // length 0 entries can't conflict
|
||||
}
|
||||
Address entryBase = getStartAddress(rec);
|
||||
if (entryBase.equals(baseAddr)) {
|
||||
if (entryLength != length) {
|
||||
throw new IllegalArgumentException(
|
||||
"new entry must have the same length as existing entry");
|
||||
}
|
||||
if ((sourceFileId << 32 | lineNumber) == getFileAndLine(rec)) {
|
||||
return getSourceMapEntry(rec); // entry is already in the DB
|
||||
}
|
||||
continue; // non-conflicting entry found, continue checking
|
||||
}
|
||||
if (isLessOrEqualInSameSpace(baseAddr, entryBase)) {
|
||||
throw new IllegalArgumentException(
|
||||
"new entry would overlap entry " + getSourceMapEntry(rec).toString());
|
||||
}
|
||||
if (isLessOrEqualInSameSpace(entryBase, baseAddr)) {
|
||||
if (getEndAddress(entryBase, entryLength).compareTo(baseAddr) >= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"new entry would overlap entry " + getSourceMapEntry(rec).toString());
|
||||
}
|
||||
}
|
||||
break; // safe to add new entry
|
||||
}
|
||||
DBRecord rec =
|
||||
sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, length);
|
||||
entry = new SourceMapEntryDB(this, rec, addrMap);
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
|
||||
return entry;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
throw new AssertionError("addSourceMapEntry unsuccessful - possible database error");
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSourceMapEntry(AddressSetView addrs) {
|
||||
if (addrs == null || addrs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
lock.acquire();
|
||||
try {
|
||||
for (AddressRangeIterator rangeIter = addrs.getAddressRanges(); rangeIter.hasNext();) {
|
||||
AddressRange r = rangeIter.next();
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(r.getMaxAddress(), false);
|
||||
while (recIter.hasPrevious()) {
|
||||
DBRecord rec = recIter.previous();
|
||||
Address entryStart = getStartAddress(rec);
|
||||
if (r.contains(entryStart)) {
|
||||
return true;
|
||||
}
|
||||
long length = getLength(rec);
|
||||
if (length == 0) {
|
||||
continue;
|
||||
}
|
||||
if (r.intersects(entryStart, getEndAddress(entryStart, length))) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbError(IOException e) throws RuntimeException {
|
||||
program.dbError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceFile> getMappedSourceFiles() {
|
||||
List<SourceFile> sourceFiles = new ArrayList<>();
|
||||
lock.acquire();
|
||||
try {
|
||||
for (RecordIterator sourceFileRecordIter =
|
||||
sourceFileTableAdapter.getRecords(); sourceFileRecordIter
|
||||
.hasNext();) {
|
||||
DBRecord sourceFileRecord = sourceFileRecordIter.next();
|
||||
long key = sourceFileRecord.getKey();
|
||||
RecordIterator sourceMapEntryRecordIter =
|
||||
sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
|
||||
if (sourceMapEntryRecordIter.hasNext()) {
|
||||
updateLastSourceFileAndLastKey(sourceFileRecord);
|
||||
sourceFiles.add(lastSourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceFile> getAllSourceFiles() {
|
||||
List<SourceFile> sourceFiles = new ArrayList<>();
|
||||
lock.acquire();
|
||||
try {
|
||||
for (RecordIterator recordIter = sourceFileTableAdapter.getRecords(); recordIter
|
||||
.hasNext();) {
|
||||
updateLastSourceFileAndLastKey(recordIter.next());
|
||||
sourceFiles.add(lastSourceFile);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferSourceMapEntries(SourceFile source, SourceFile target)
|
||||
throws LockException {
|
||||
program.checkExclusiveAccess();
|
||||
|
||||
lock.acquire();
|
||||
try {
|
||||
Long srcKey = getKeyForSourceFile(source);
|
||||
if (srcKey == null) {
|
||||
throw new IllegalArgumentException(
|
||||
source.toString() + " is not associated with program");
|
||||
}
|
||||
Long targetKey = getKeyForSourceFile(target);
|
||||
if (targetKey == null) {
|
||||
throw new IllegalArgumentException(
|
||||
target.toString() + " is not associated with program");
|
||||
}
|
||||
if (source.equals(target)) {
|
||||
return; // transfer redundant
|
||||
}
|
||||
for (SourceMapEntry entry : getSourceMapEntries(source, 0, Integer.MAX_VALUE)) {
|
||||
addSourceMapEntry(target, entry.getLineNumber(), entry.getBaseAddress(),
|
||||
entry.getLength());
|
||||
removeSourceMapEntry(entry); // remove fires a SOURCE_MAP_CHANGED event
|
||||
}
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
// can't happen - entry ranges were validated upon insert
|
||||
throw new AssertionError("bad address range in source map entry table");
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
|
||||
try {
|
||||
return new SourceMapEntryIteratorDB(this,
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(address, forward), forward);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
return SourceMapEntryIterator.EMPTY_ITERATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSourceFile(SourceFile sourceFile) {
|
||||
if (sourceFile == null) {
|
||||
return false;
|
||||
}
|
||||
lock.acquire();
|
||||
try {
|
||||
return getKeyForSourceFile(sourceFile) != null;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
|
||||
int maxLine) {
|
||||
if (minLine < 0) {
|
||||
throw new IllegalArgumentException("minLine cannot be negative; was " + minLine);
|
||||
}
|
||||
if (maxLine < 0) {
|
||||
throw new IllegalArgumentException("maxLine cannot be negative; was " + maxLine);
|
||||
}
|
||||
if (maxLine < minLine) {
|
||||
throw new IllegalArgumentException("maxLine cannot be less than minLine");
|
||||
}
|
||||
List<SourceMapEntry> entries = new ArrayList<>();
|
||||
lock.acquire();
|
||||
try {
|
||||
Long key = getKeyForSourceFile(sourceFile);
|
||||
if (key == null) {
|
||||
return entries;
|
||||
}
|
||||
try {
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getRecordsForSourceFile(key, minLine, maxLine);
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
entries.add(getSourceMapEntry(rec));
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
Collections.sort(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
|
||||
Objects.requireNonNull(entry, "entry cannot be null");
|
||||
program.checkExclusiveAccess();
|
||||
lock.acquire();
|
||||
try {
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(entry.getBaseAddress(), true);
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
long length = getLength(rec);
|
||||
if (length != entry.getLength()) {
|
||||
continue;
|
||||
}
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
if (((int) (fileAndLine & 0xffffffff)) != entry.getLineNumber()) {
|
||||
continue;
|
||||
}
|
||||
if (!(entry.getSourceFile().equals(getSourceFileFromKey(fileAndLine >> 32)))) {
|
||||
continue;
|
||||
}
|
||||
sourceMapTableAdapter.removeRecord(rec.getKey());
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, entry, null);
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceFile getSourceFile(long key) {
|
||||
lock.acquire();
|
||||
try {
|
||||
return getSourceFileFromKey(key);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
SourceMapEntry getSourceMapEntry(DBRecord rec) {
|
||||
return new SourceMapEntryDB(this, rec, addrMap);
|
||||
}
|
||||
|
||||
static boolean isLessOrEqualInSameSpace(Address addr1, Address addr2) {
|
||||
if (!addr1.hasSameAddressSpace(addr2)) {
|
||||
return false;
|
||||
}
|
||||
return addr1.compareTo(addr2) <= 0;
|
||||
}
|
||||
|
||||
static boolean isLessInSameSpace(Address addr1, Address addr2) {
|
||||
if (!addr1.hasSameAddressSpace(addr2)) {
|
||||
return false;
|
||||
}
|
||||
return addr1.compareTo(addr2) < 0;
|
||||
}
|
||||
|
||||
// acquire lock before invoking
|
||||
private SourceMapEntry addZeroLengthEntry(long sourceFileId, int lineNumber, Address baseAddr) {
|
||||
SourceMapEntry entry = null;
|
||||
try {
|
||||
RecordIterator recIter =
|
||||
sourceMapTableAdapter.getSourceMapRecordIterator(baseAddr, true);
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
Address recAddress = getStartAddress(rec);
|
||||
if (!recAddress.equals(baseAddr)) {
|
||||
break;
|
||||
}
|
||||
if (getLength(rec) != 0) {
|
||||
continue;
|
||||
}
|
||||
long fileAndLine = getFileAndLine(rec);
|
||||
if ((sourceFileId << 32 | lineNumber) != fileAndLine) {
|
||||
continue;
|
||||
}
|
||||
return getSourceMapEntry(rec);
|
||||
}
|
||||
DBRecord rec = sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, 0);
|
||||
entry = new SourceMapEntryDB(this, rec, addrMap);
|
||||
program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
|
||||
return entry;
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
throw new AssertionError("addZeroLengthEntry unsuccessful - possible database error");
|
||||
}
|
||||
}
|
||||
|
||||
private long getLength(DBRecord rec) {
|
||||
return rec.getLongValue(SourceMapAdapter.LENGTH_COL);
|
||||
}
|
||||
|
||||
private Address getStartAddress(DBRecord rec) {
|
||||
return addrMap.decodeAddress(rec.getLongValue(SourceMapAdapter.BASE_ADDR_COL));
|
||||
}
|
||||
|
||||
private long getFileAndLine(DBRecord rec) {
|
||||
return rec.getLongValue(SourceMapAdapter.FILE_LINE_COL);
|
||||
}
|
||||
|
||||
// assumes that start and length are from a valid SourceMapEntry
|
||||
// that is, start.add(length - 1) doesn't wrap and is in the same
|
||||
// space
|
||||
private Address getEndAddress(Address start, long length) {
|
||||
if (length == 0) {
|
||||
return start;
|
||||
}
|
||||
try {
|
||||
return start.addNoWrap(length - 1);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
// shouldn't happen, but return space max to prevent possibility of wrapping
|
||||
return start.getAddressSpace().getMaxAddress();
|
||||
}
|
||||
}
|
||||
|
||||
// acquire lock before invoking
|
||||
private SourceFile getSourceFileFromKey(long key) {
|
||||
if (lastKey == null || lastKey.longValue() != key) {
|
||||
DBRecord dbRecord = null;
|
||||
try {
|
||||
dbRecord = sourceFileTableAdapter.getRecord(key);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return null;
|
||||
}
|
||||
if (dbRecord == null) {
|
||||
return null;
|
||||
}
|
||||
updateLastSourceFileAndLastKey(dbRecord);
|
||||
}
|
||||
return lastSourceFile;
|
||||
}
|
||||
|
||||
// acquire lock before invoking
|
||||
private Long getKeyForSourceFile(SourceFile sourceFile) {
|
||||
if (lastSourceFile == null || !sourceFile.equals(lastSourceFile)) {
|
||||
DBRecord dbRecord = null;
|
||||
try {
|
||||
dbRecord = sourceFileTableAdapter.getRecord(sourceFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
return null;
|
||||
}
|
||||
if (dbRecord == null) {
|
||||
return null;
|
||||
}
|
||||
updateLastSourceFileAndLastKey(dbRecord);
|
||||
}
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
private void updateLastSourceFileAndLastKey(DBRecord dbRecord) {
|
||||
lastKey = dbRecord.getKey();
|
||||
String path = dbRecord.getString(SourceFileAdapter.PATH_COL);
|
||||
SourceFileIdType idType =
|
||||
SourceFileIdType.getTypeFromIndex(dbRecord.getByteValue(SourceFileAdapter.ID_TYPE_COL));
|
||||
byte[] identifier = dbRecord.getBinaryData(SourceFileAdapter.ID_COL);
|
||||
lastSourceFile = new SourceFile(path, idType, identifier, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A record for storing information about new source map entries which must be created
|
||||
* during {@link #moveAddressRange} or {@link #deleteAddressRange}
|
||||
*/
|
||||
private record SourceMapEntryData(long sourceFileId, int lineNumber, Address baseAddress,
|
||||
long length) {}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.program.database.map.AddressMapDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.sourcemap.SourceFileManager;
|
||||
import ghidra.program.model.sourcemap.SourceMapEntry;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Base class for adapters to access the Source Map table.
|
||||
* <p>
|
||||
* Each entry in the table corresponds to a single {@link SourceMapEntry} and so records
|
||||
* a {@link SourceFile}, a line number, a base address, and a length.
|
||||
* <p>
|
||||
* There are a number of restrictions on a {@link SourceMapEntry}, which are listed in that
|
||||
* interface's top-level documentation. It is the responsibility of the {@link SourceFileManager}
|
||||
* to enforce these restrictions.
|
||||
*/
|
||||
abstract class SourceMapAdapter {
|
||||
|
||||
static final String TABLE_NAME = "SourceMap";
|
||||
static final int FILE_LINE_COL = SourceMapAdapterV0.V0_FILE_LINE_COL;
|
||||
static final int BASE_ADDR_COL = SourceMapAdapterV0.V0_BASE_ADDR_COL;
|
||||
static final int LENGTH_COL = SourceMapAdapterV0.V0_LENGTH_COL;
|
||||
|
||||
/**
|
||||
* Creates an adapter for the Source Map table.
|
||||
* @param dbh database handle
|
||||
* @param addrMap address map
|
||||
* @param openMode mode
|
||||
* @param monitor task monitor
|
||||
* @return adapter for table
|
||||
* @throws VersionException if version incompatible
|
||||
*/
|
||||
static SourceMapAdapter getAdapter(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode,
|
||||
TaskMonitor monitor) throws VersionException {
|
||||
return new SourceMapAdapterV0(dbh, addrMap, openMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a record from the table
|
||||
* @param key key of record to remove.
|
||||
* @return true if the record was deleted successfully
|
||||
* @throws IOException if database error occurs
|
||||
*/
|
||||
abstract boolean removeRecord(long key) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a {@link RecordIterator} based at {@code addr}.
|
||||
* @param addr starting address
|
||||
* @param before if true, initial position is before addr, otherwise after
|
||||
* @return iterator
|
||||
* @throws IOException if database error occurs
|
||||
*/
|
||||
abstract RecordIterator getSourceMapRecordIterator(Address addr, boolean before)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a {@link RecordIterator} over all records for the source file with
|
||||
* id {@code id}, subject to the line bounds {@code minLine} and {@code maxLine}
|
||||
* @param fileId id of source file
|
||||
* @param minLine minimum line number
|
||||
* @param maxLine maximum line number
|
||||
* @return iterator
|
||||
* @throws IOException if database error occurs
|
||||
*/
|
||||
abstract RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Adds an entry to the source map table. This method assumes that no address
|
||||
* in the associated range has already been associated with this source file and
|
||||
* line number.
|
||||
* @param fileId source file id
|
||||
* @param lineNum line number
|
||||
* @param baseAddr minimum address of range
|
||||
* @param length number of addresses in range
|
||||
* @return record
|
||||
* @throws IOException if database error occurs
|
||||
*/
|
||||
abstract DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Updates all appropriate entries in the table when an address range is moved.
|
||||
* @param fromAddr from address
|
||||
* @param toAddr to address
|
||||
* @param length number of addresses in moved range
|
||||
* @param monitor task monitor
|
||||
* @throws CancelledException if task cancelled
|
||||
* @throws IOException if database error occurs
|
||||
*/
|
||||
abstract void moveAddressRange(Address fromAddr, Address toAddr, long length,
|
||||
TaskMonitor monitor) throws CancelledException, IOException;
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
import ghidra.framework.data.OpenMode;
|
||||
import ghidra.program.database.map.AddressIndexPrimaryKeyIterator;
|
||||
import ghidra.program.database.map.AddressMapDB;
|
||||
import ghidra.program.database.util.DatabaseTableUtils;
|
||||
import ghidra.program.database.util.EmptyRecordIterator;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Initial version of {@link SourceMapAdapter}
|
||||
*/
|
||||
class SourceMapAdapterV0 extends SourceMapAdapter implements DBListener {
|
||||
|
||||
final static int SCHEMA_VERSION = 0;
|
||||
static final int V0_FILE_LINE_COL = 0; // indexed
|
||||
static final int V0_BASE_ADDR_COL = 1; // indexed
|
||||
static final int V0_LENGTH_COL = 2;
|
||||
|
||||
// key | ((32-bit source file id << 32) | 32-bit line number) | base addr | length
|
||||
private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
|
||||
new Field[] { LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE },
|
||||
new String[] { "fileAndLine", "baseAddress", "length" }, null);
|
||||
|
||||
private static final int[] INDEXED_COLUMNS = new int[] { V0_FILE_LINE_COL, V0_BASE_ADDR_COL };
|
||||
|
||||
private Table table; // lazy creation, null if empty
|
||||
private final DBHandle dbHandle;
|
||||
private AddressMapDB addrMap;
|
||||
|
||||
/**
|
||||
* Creates an adapter for version 0 of the source map adapter
|
||||
* @param dbh database handle
|
||||
* @param addrMap address map
|
||||
* @param openMode open mode
|
||||
* @throws VersionException if version incompatible
|
||||
*/
|
||||
SourceMapAdapterV0(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode)
|
||||
throws VersionException {
|
||||
this.dbHandle = dbh;
|
||||
this.addrMap = addrMap;
|
||||
|
||||
// As in FunctionTagAdapterV0, we need to add this as a database listener.
|
||||
// Since the table is created lazily, undoing a transaction which (for example) caused
|
||||
// the table to be created can leave the table in a bad state.
|
||||
// The implementation of dbRestored(DBHandle) solves this issue.
|
||||
this.dbHandle.addListener(this);
|
||||
|
||||
if (!openMode.equals(OpenMode.CREATE)) {
|
||||
table = dbHandle.getTable(TABLE_NAME);
|
||||
if (table == null) {
|
||||
return; // perform lazy table creation
|
||||
}
|
||||
int version = table.getSchema().getVersion();
|
||||
if (version != SCHEMA_VERSION) {
|
||||
throw new VersionException(VersionException.NEWER_VERSION, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbRestored(DBHandle dbh) {
|
||||
table = dbh.getTable(TABLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dbClosed(DBHandle dbh) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tableDeleted(DBHandle dbh, Table t) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tableAdded(DBHandle dbh, Table t) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean removeRecord(long key) throws IOException {
|
||||
if (table != null) {
|
||||
return table.deleteRecord(key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
RecordIterator getSourceMapRecordIterator(Address addr, boolean before) throws IOException {
|
||||
if (table == null || addr == null) {
|
||||
return EmptyRecordIterator.INSTANCE;
|
||||
}
|
||||
AddressIndexPrimaryKeyIterator keyIter =
|
||||
new AddressIndexPrimaryKeyIterator(table, V0_BASE_ADDR_COL, addrMap, addr, before);
|
||||
return new KeyToRecordIterator(table, keyIter);
|
||||
}
|
||||
|
||||
@Override
|
||||
RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
|
||||
throws IOException {
|
||||
if (table == null) {
|
||||
return EmptyRecordIterator.INSTANCE;
|
||||
}
|
||||
fileId = fileId << 32;
|
||||
LongField minField = new LongField(fileId | minLine);
|
||||
LongField maxField = new LongField(fileId | maxLine);
|
||||
return table.indexIterator(V0_FILE_LINE_COL, minField, maxField, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
|
||||
throws IOException {
|
||||
DBRecord rec = V0_SCHEMA.createRecord(getTable().getKey());
|
||||
rec.setLongValue(V0_FILE_LINE_COL, (fileId << 32) | lineNum);
|
||||
rec.setLongValue(V0_BASE_ADDR_COL, addrMap.getKey(baseAddr, true));
|
||||
rec.setLongValue(V0_LENGTH_COL, length);
|
||||
table.putRecord(rec);
|
||||
return rec;
|
||||
}
|
||||
|
||||
@Override
|
||||
void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
if (table == null) {
|
||||
return;
|
||||
}
|
||||
DatabaseTableUtils.updateIndexedAddressField(table, V0_BASE_ADDR_COL, addrMap, fromAddr,
|
||||
toAddr, length, null, monitor);
|
||||
}
|
||||
|
||||
private Table getTable() throws IOException {
|
||||
if (table == null) {
|
||||
table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.program.database.map.AddressMapDB;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.sourcemap.SourceMapEntry;
|
||||
|
||||
/**
|
||||
* Database implementation of {@link SourceMapEntry} interface.
|
||||
* <p>
|
||||
* Note: clients should drop and reacquire all SourceMapEntryDB objects upon undo/redo,
|
||||
* ProgramEvent.SOURCE_MAP_CHANGED, and ProgramEvent.SOURCE_FILE_REMOVED.
|
||||
*/
|
||||
public class SourceMapEntryDB implements SourceMapEntry {
|
||||
|
||||
private int lineNumber;
|
||||
private SourceFile sourceFile;
|
||||
private Address baseAddress;
|
||||
private long length;
|
||||
private AddressRange range = null;
|
||||
|
||||
/**
|
||||
* Creates a new SourceMapEntryDB
|
||||
* @param manager source file manager
|
||||
* @param record backing record
|
||||
* @param addrMap address map
|
||||
*/
|
||||
SourceMapEntryDB(SourceFileManagerDB manager, DBRecord record, AddressMapDB addrMap) {
|
||||
manager.lock.acquire();
|
||||
try {
|
||||
long fileAndLine = record.getLongValue(SourceMapAdapter.FILE_LINE_COL);
|
||||
lineNumber = (int) (fileAndLine & 0xffffffff);
|
||||
sourceFile = manager.getSourceFile(fileAndLine >> 32);
|
||||
long encodedAddress = record.getLongValue(SourceMapAdapter.BASE_ADDR_COL);
|
||||
baseAddress = addrMap.decodeAddress(encodedAddress);
|
||||
length = record.getLongValue(SourceMapAdapter.LENGTH_COL);
|
||||
if (length != 0) {
|
||||
Address max;
|
||||
try {
|
||||
max = baseAddress.addNoWrap(length - 1);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
// shouldn't happen, but return space max to prevent possibility of wrapping
|
||||
max = baseAddress.getAddressSpace().getMaxAddress();
|
||||
}
|
||||
range = new AddressRangeImpl(baseAddress, max);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
manager.lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceFile getSourceFile() {
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getBaseAddress() {
|
||||
return baseAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer(getSourceFile().toString());
|
||||
sb.append(":");
|
||||
sb.append(getLineNumber());
|
||||
sb.append(" @ ");
|
||||
sb.append(getBaseAddress().toString());
|
||||
sb.append(" (");
|
||||
sb.append(Long.toString(getLength()));
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SourceMapEntry o) {
|
||||
int sourceFileCompare = getSourceFile().compareTo(o.getSourceFile());
|
||||
if (sourceFileCompare != 0) {
|
||||
return sourceFileCompare;
|
||||
}
|
||||
int lineCompare = Integer.compare(getLineNumber(), o.getLineNumber());
|
||||
if (lineCompare != 0) {
|
||||
return lineCompare;
|
||||
}
|
||||
int addrCompare = getBaseAddress().compareTo(o.getBaseAddress());
|
||||
if (addrCompare != 0) {
|
||||
return addrCompare;
|
||||
}
|
||||
return Long.compareUnsigned(getLength(), o.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SourceMapEntry otherEntry)) {
|
||||
return false;
|
||||
}
|
||||
if (lineNumber != otherEntry.getLineNumber()) {
|
||||
return false;
|
||||
}
|
||||
if (!sourceFile.equals(otherEntry.getSourceFile())) {
|
||||
return false;
|
||||
}
|
||||
if (!baseAddress.equals(otherEntry.getBaseAddress())) {
|
||||
return false;
|
||||
}
|
||||
if (length != otherEntry.getLength()) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(range, otherEntry.getRange())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = lineNumber;
|
||||
hashCode = 31 * hashCode + sourceFile.hashCode();
|
||||
hashCode = 31 * hashCode + baseAddress.hashCode();
|
||||
hashCode = 31 * hashCode + Long.hashCode(length);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.database.sourcemap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import db.DBRecord;
|
||||
import db.RecordIterator;
|
||||
import ghidra.program.model.sourcemap.SourceMapEntry;
|
||||
import ghidra.program.model.sourcemap.SourceMapEntryIterator;
|
||||
|
||||
/**
|
||||
* Database implementation of {@link SourceMapEntryIterator}
|
||||
*/
|
||||
public class SourceMapEntryIteratorDB implements SourceMapEntryIterator {
|
||||
|
||||
private boolean forward;
|
||||
private RecordIterator recIter;
|
||||
private SourceFileManagerDB sourceManager;
|
||||
private SourceMapEntry nextEntry;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param sourceManager source manager
|
||||
* @param recIter record iterator
|
||||
* @param forward direction to iterate
|
||||
*/
|
||||
SourceMapEntryIteratorDB(SourceFileManagerDB sourceManager, RecordIterator recIter,
|
||||
boolean forward) {
|
||||
this.sourceManager = sourceManager;
|
||||
this.recIter = recIter;
|
||||
this.forward = forward;
|
||||
this.nextEntry = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (nextEntry != null) {
|
||||
return true;
|
||||
}
|
||||
sourceManager.lock.acquire();
|
||||
try {
|
||||
boolean recIterNext = forward ? recIter.hasNext() : recIter.hasPrevious();
|
||||
if (!recIterNext) {
|
||||
return false;
|
||||
}
|
||||
DBRecord rec = forward ? recIter.next() : recIter.previous();
|
||||
nextEntry = sourceManager.getSourceMapEntry(rec);
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
sourceManager.dbError(e);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
sourceManager.lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntry next() {
|
||||
if (hasNext()) {
|
||||
SourceMapEntry entryToReturn = nextEntry;
|
||||
nextEntry = null;
|
||||
return entryToReturn;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<SourceMapEntry> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
|
@ -105,7 +105,7 @@ public class AddressRangeMapDB implements DBListener {
|
|||
* @param errHandler database error handler
|
||||
* @param valueField specifies the type for the values stored in this map
|
||||
* @param indexed if true, values will be indexed allowing use of the
|
||||
* getValueRangeIterator method
|
||||
* {@link AddressRangeMapDB#getAddressSet(Field)} method.
|
||||
*/
|
||||
public AddressRangeMapDB(DBHandle dbHandle, AddressMap addressMap, Lock lock, String name,
|
||||
ErrorHandler errHandler, Field valueField, boolean indexed) {
|
||||
|
@ -343,7 +343,8 @@ public class AddressRangeMapDB implements DBListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns set of addresses where the given value has been set
|
||||
* Returns set of addresses where the given value has been set.
|
||||
* This method may only be invoked on indexed {@link AddressRangeMapDB}s!
|
||||
* @param value the value to search for
|
||||
* @return set of addresses where the given value has been set
|
||||
*/
|
||||
|
|
|
@ -28,6 +28,7 @@ import ghidra.program.model.lang.*;
|
|||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.program.model.reloc.RelocationTable;
|
||||
import ghidra.program.model.sourcemap.SourceFileManager;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.model.util.AddressSetPropertyMap;
|
||||
import ghidra.program.model.util.PropertyMapManager;
|
||||
|
@ -149,6 +150,14 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
|
|||
*/
|
||||
public BookmarkManager getBookmarkManager();
|
||||
|
||||
/**
|
||||
* Returns the program's {@link SourceFileManager}.
|
||||
* @return the source file manager
|
||||
*/
|
||||
default public SourceFileManager getSourceFileManager() {
|
||||
return SourceFileManager.DUMMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default pointer size in bytes as it may be stored within the program listing.
|
||||
* @return default pointer size.
|
||||
|
@ -535,4 +544,5 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
|
|||
* @return unique program ID
|
||||
*/
|
||||
public long getUniqueProgramID();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.sourcemap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.sourcemap.SourceFile;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
|
||||
/**
|
||||
* A "dummy" implementation of {@link SourceFileManager}.
|
||||
*/
|
||||
public class DummySourceFileManager implements SourceFileManager {
|
||||
|
||||
public DummySourceFileManager() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceMapEntry> getSourceMapEntries(Address addr) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
|
||||
long length) throws LockException {
|
||||
throw new UnsupportedOperationException("Cannot add source map entries with this manager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSourceMapEntry(AddressSetView addrs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceFile> getAllSourceFiles() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceFile> getMappedSourceFiles() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferSourceMapEntries(SourceFile source, SourceFile target) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Dummy source file manager cannot transfer map info");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
|
||||
return SourceMapEntryIterator.EMPTY_ITERATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
|
||||
int maxLine) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addSourceFile(SourceFile sourceFile) throws LockException {
|
||||
throw new UnsupportedOperationException("cannot add source files to this manager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
|
||||
throw new UnsupportedOperationException("cannot remove source files from this manager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSourceFile(SourceFile sourceFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
|
||||
throw new UnsupportedOperationException(
|
||||
"cannot remove source map entries from this manager");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.sourcemap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.sourcemap.SourceFile;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
/**
|
||||
* This interface defines methods for managing {@link SourceFile}s and {@link SourceMapEntry}s.
|
||||
*/
|
||||
public interface SourceFileManager {
|
||||
|
||||
public static final SourceFileManager DUMMY = new DummySourceFileManager();
|
||||
|
||||
/**
|
||||
* Returns a sorted list of {@link SourceMapEntry}s associated with an address {@code addr}.
|
||||
* @param addr address
|
||||
* @return line number
|
||||
*/
|
||||
public List<SourceMapEntry> getSourceMapEntries(Address addr);
|
||||
|
||||
/**
|
||||
* Adds a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
|
||||
* line number {@code lineNumber}, and {@link AddressRange} {@code range} to the program
|
||||
* database.
|
||||
* <p>
|
||||
* Entries with non-zero lengths must either cover the same address range or be disjoint.
|
||||
* @param sourceFile source file
|
||||
* @param lineNumber line number
|
||||
* @param range address range
|
||||
* @return created SourceMapEntry
|
||||
* @throws LockException if invoked without exclusive access
|
||||
* @throws IllegalArgumentException if the range of the new entry intersects, but does
|
||||
* not equal, the range of an existing entry or if sourceFile was not previously added
|
||||
* to the program.
|
||||
* @throws AddressOutOfBoundsException if the range of the new entry contains addresses
|
||||
* that are not in a defined memory block
|
||||
*/
|
||||
public default SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber,
|
||||
AddressRange range) throws LockException {
|
||||
try {
|
||||
return addSourceMapEntry(sourceFile, lineNumber, range.getMinAddress(),
|
||||
range.getLength());
|
||||
}
|
||||
// can't happen
|
||||
catch (AddressOverflowException e) {
|
||||
throw new AssertionError("Address overflow with valid AddressRange");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
|
||||
* line number {@code lineNumber}, and non-negative length {@code length} and
|
||||
* adds it to the program database.
|
||||
* <p>
|
||||
* Entries with non-zero lengths must either cover the same address range or be disjoint.
|
||||
* @param sourceFile source file
|
||||
* @param lineNumber line number
|
||||
* @param baseAddr minimum address of range
|
||||
* @param length number of addresses in range
|
||||
* @return created SourceMapEntry
|
||||
* @throws AddressOverflowException if baseAddr + length-1 overflows
|
||||
* @throws LockException if invoked without exclusive access
|
||||
* @throws IllegalArgumentException if the range of the new entry intersects, but does
|
||||
* not equal, the range of an existing entry or if sourceFile was not previously added to the
|
||||
* program.
|
||||
* @throws AddressOutOfBoundsException if the range of the new entry contains addresses
|
||||
* that are not in a defined memory block
|
||||
*/
|
||||
public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
|
||||
long length) throws AddressOverflowException, LockException;
|
||||
|
||||
/**
|
||||
* Returns {@code true} precisely when at least one {@link Address} in {@code addrs} has
|
||||
* source map information.
|
||||
* @param addrs addresses to check
|
||||
* @return true when at least one address has source map info
|
||||
*/
|
||||
public boolean intersectsSourceMapEntry(AddressSetView addrs);
|
||||
|
||||
/**
|
||||
* Adds a {@link SourceFile} to this manager. A SourceFile must be added before it can be
|
||||
* associated with any source map information.
|
||||
*
|
||||
* @param sourceFile source file to add (can't be null)
|
||||
* @return true if this manager did not already contain sourceFile
|
||||
* @throws LockException if invoked without exclusive access
|
||||
*/
|
||||
public boolean addSourceFile(SourceFile sourceFile) throws LockException;
|
||||
|
||||
/**
|
||||
* Removes a {@link SourceFile} from this manager. Any associated {@link SourceMapEntry}s will
|
||||
* also be removed.
|
||||
* @param sourceFile source file to remove
|
||||
* @return true if sourceFile was in the manager
|
||||
* @throws LockException if invoked without exclusive access
|
||||
*/
|
||||
public boolean removeSourceFile(SourceFile sourceFile) throws LockException;
|
||||
|
||||
/**
|
||||
* Returns true precisely when this manager contains {@code sourceFile}.
|
||||
* @param sourceFile source file
|
||||
* @return true if source file already added
|
||||
*/
|
||||
public boolean containsSourceFile(SourceFile sourceFile);
|
||||
|
||||
/**
|
||||
* Returns a {@link List} containing all {@link SourceFile}s of the program.
|
||||
* @return source file list
|
||||
*/
|
||||
public List<SourceFile> getAllSourceFiles();
|
||||
|
||||
/**
|
||||
* Returns a {@link List} containing {@link SourceFile}s which are
|
||||
* mapped to at least one address in the program
|
||||
* @return mapped source file list
|
||||
*/
|
||||
public List<SourceFile> getMappedSourceFiles();
|
||||
|
||||
/**
|
||||
* Changes the source map so that any {@link SourceMapEntry} associated with {@code source}
|
||||
* is associated with {@code target} instead. Any entries associated with
|
||||
* {@code target} before invocation will still be associated with
|
||||
* {@code target} after invocation. {@code source} will not be associated
|
||||
* with any entries after invocation (unless {@code source} and {@code target}
|
||||
* are the same). Line number information is not changed.
|
||||
* @param source source file to get info from
|
||||
* @param target source file to move info to
|
||||
* @throws LockException if invoked without exclusive access
|
||||
* @throws IllegalArgumentException if source or target has not been added previously
|
||||
*/
|
||||
public void transferSourceMapEntries(SourceFile source, SourceFile target) throws LockException;
|
||||
|
||||
/**
|
||||
* Returns a {@link SourceMapEntryIterator} starting at {@code address}.
|
||||
*
|
||||
* @param address starting address
|
||||
* @param forward direction of iterator (true = forward)
|
||||
* @return iterator
|
||||
*/
|
||||
public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward);
|
||||
|
||||
/**
|
||||
* Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
|
||||
* between {@code minLine} and {@code maxLine}, inclusive.
|
||||
* @param sourceFile source file
|
||||
* @param minLine minimum line number
|
||||
* @param maxLine maximum line number
|
||||
* @return source map entries
|
||||
*/
|
||||
public List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int minLine,
|
||||
int maxLine);
|
||||
|
||||
/**
|
||||
* Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
|
||||
* equal to {@code lineNumber}.
|
||||
* @param sourceFile source file
|
||||
* @param lineNumber line number
|
||||
* @return source map entries
|
||||
*/
|
||||
public default List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile, int lineNumber) {
|
||||
return getSourceMapEntries(sourceFile, lineNumber, lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted of list all {@link SourceMapEntry}s in the program corresponding to
|
||||
* {@code sourceFile}.
|
||||
* @param sourceFile source file
|
||||
* @return source map entries
|
||||
*/
|
||||
public default List<SourceMapEntry> getSourceMapEntries(SourceFile sourceFile) {
|
||||
return getSourceMapEntries(sourceFile, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link SourceMapEntry} from this manager.
|
||||
* @param entry entry to remove
|
||||
* @return true if entry was in the manager
|
||||
* @throws LockException if invoked without exclusive access
|
||||
*/
|
||||
public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException;
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.sourcemap;
|
||||
|
||||
import ghidra.program.database.sourcemap.SourceFile;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
|
||||
/**
|
||||
* A SourceMapEntry consists of a {@link SourceFile}, a line number, a base address,
|
||||
* and a length. If the length is positive, the base address and the length determine
|
||||
* an {@link AddressRange}. In this case, the length of a {@code SourceMapEntry} is the
|
||||
* length of the associated {@link AddressRange}, i.e., the number of {@link Address}es in
|
||||
* the range (see {@link AddressRange#getLength()}). The intent is that the range
|
||||
* contains all of the bytes corresponding to a given line of source. The length of a
|
||||
* {@code SourceMapEntry} can be 0, in which case the associated range is null. Negative
|
||||
* lengths are not allowed.
|
||||
* <p>
|
||||
* The baseAddress of a range must occur within a memory block of the program, as must each
|
||||
* address within the range of a {@code SourceMapEntry}. A range may span multiple
|
||||
* (contiguous) memory blocks.
|
||||
* <p>
|
||||
* If the ranges of two entries (with non-zero lengths) intersect, then the ranges must be
|
||||
* identical. The associated {@link SourceFile}s and/or line numbers can be different.
|
||||
* <p>
|
||||
* Entries with length zero do not conflict with other entries and may occur within the
|
||||
* range of another entry.
|
||||
* <p>
|
||||
* For a fixed source file, line number, base address, and length, there must be only one
|
||||
* SourceMapEntry.
|
||||
* <p>
|
||||
* SourceMapEntry objects are created using the {@link SourceFileManager} for a program,
|
||||
* which must enforce the restrictions listed above.
|
||||
*/
|
||||
public interface SourceMapEntry extends Comparable<SourceMapEntry> {
|
||||
|
||||
/**
|
||||
* Returns the line number.
|
||||
* @return line number
|
||||
*/
|
||||
public int getLineNumber();
|
||||
|
||||
/**
|
||||
* Returns the source file
|
||||
* @return source file
|
||||
*/
|
||||
public SourceFile getSourceFile();
|
||||
|
||||
/**
|
||||
* Returns the base address of the entry
|
||||
* @return base address
|
||||
*/
|
||||
public Address getBaseAddress();
|
||||
|
||||
/**
|
||||
* Returns the length of the range (number of addresses)
|
||||
* @return length
|
||||
*/
|
||||
public long getLength();
|
||||
|
||||
/**
|
||||
* Returns the address range, or null for length 0 entries
|
||||
* @return address range or null
|
||||
*/
|
||||
public AddressRange getRange();
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.sourcemap;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Interface for iterating over {@link SourceMapEntry}s.
|
||||
*/
|
||||
public interface SourceMapEntryIterator extends Iterator<SourceMapEntry>, Iterable<SourceMapEntry> {
|
||||
|
||||
public static final SourceMapEntryIterator EMPTY_ITERATOR = new SourceMapEntryIterator() {
|
||||
|
||||
@Override
|
||||
public Iterator<SourceMapEntry> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceMapEntry next() {
|
||||
throw new NoSuchElementException("Empty iterator is empty!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -17,7 +17,6 @@ package ghidra.program.model.symbol;
|
|||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.*;
|
||||
|
@ -137,7 +136,7 @@ public class SymbolUtilities {
|
|||
|
||||
/**
|
||||
* Check for invalid characters
|
||||
* (space, colon, asterisk, plus, bracket)
|
||||
* (space or unprintable ascii below 0x20)
|
||||
* in labels.
|
||||
*
|
||||
* @param str the string to be checked for invalid characters.
|
||||
|
|
|
@ -147,7 +147,11 @@ public enum ProgramEvent implements EventType {
|
|||
CODE_UNIT_USER_DATA_CHANGED, // user data has changed for some code unit
|
||||
USER_DATA_CHANGED, // general user data has changed at some address
|
||||
|
||||
RELOCATION_ADDED; // a relocation entry was added
|
||||
RELOCATION_ADDED, // a relocation entry was added
|
||||
|
||||
SOURCE_FILE_ADDED, // a source file was added
|
||||
SOURCE_FILE_REMOVED, // a source file was removed
|
||||
SOURCE_MAP_CHANGED; // source map information was changed
|
||||
|
||||
private final int id = DomainObjectEventIdGenerator.next();
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.util;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.sourcemap.SourceMapEntry;
|
||||
|
||||
/**
|
||||
* A {@link ProgramLocation} for source map information.
|
||||
*/
|
||||
public class SourceMapFieldLocation extends ProgramLocation {
|
||||
|
||||
private SourceMapEntry sourceMapEntry;
|
||||
|
||||
public SourceMapFieldLocation(Program program, Address addr, int row, int charOffset,
|
||||
SourceMapEntry sourceMapEntry) {
|
||||
super(program, addr, row, 0, charOffset);
|
||||
this.sourceMapEntry = sourceMapEntry;
|
||||
}
|
||||
|
||||
public SourceMapFieldLocation() {
|
||||
// for deserialization
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SourceMapEntry} associated with this location.
|
||||
* @return source map entry
|
||||
*/
|
||||
public SourceMapEntry getSourceMapEntry() {
|
||||
return sourceMapEntry;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue