diff --git a/Ghidra/Features/BSim/ghidra_scripts/CompareExecutablesScript.java b/Ghidra/Features/BSim/ghidra_scripts/CompareExecutablesScript.java index 1b01cf79b9..43f8bf8ec4 100755 --- a/Ghidra/Features/BSim/ghidra_scripts/CompareExecutablesScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/CompareExecutablesScript.java @@ -13,33 +13,87 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// Calculate similarity/signifigance scores between executables by +// Calculate similarity/significance scores between executables by // combining their function scores. //@category BSim +import java.io.IOException; import java.net.URL; +import java.security.InvalidParameterException; import ghidra.app.script.GhidraScript; import ghidra.features.bsim.query.BSimClientFactory; import ghidra.features.bsim.query.FunctionDatabase; import ghidra.features.bsim.query.client.*; import ghidra.features.bsim.query.description.ExecutableRecord; +import ghidra.features.bsim.query.protocol.QueryExeInfo; +import ghidra.features.bsim.query.protocol.ResponseExe; +/** + * An example script using {@link ExecutableComparison} to compare executables within a BSim database. + * The user provides the URL of the database and the name of an executable within the database that + * will be compared against every other executable. + * + * Executables are considered similar if they share similar functions, as determined by the BSim similarity metric. + * Functions that are too common (high hitcount) or are too small (low self signficance) are not included + * in the score. A score of 1.0 means that all functions included in the score are shared between the two + * executables and each have a (function) similarity of 1.0. + * + * The script also computes a "library" score, which achieves 1.0 if the functions in the smaller of the two + * executables all have a perfect match in the bigger executable. The bigger executable may have many functions + * with no match in the smaller executable. + * + * For larger databases, repeated runs of this script can be made more efficient by allowing it to cache executable + * "self-scores" between runs. Uncomment one of two lines instantiating the {@link ScoreCaching} object below. The + * cache can be stored in the local file system or in an additional table/column in the BSim database. + */ public class CompareExecutablesScript extends GhidraScript { private ExecutableComparison exeCompare; @Override protected void run() throws Exception { - URL url = BSimClientFactory.deriveBSimURL("ghidra://localhost/repo"); + String urlString = askString("Enter BSim database URL", "URL: "); + String execName = + askString("Enter name of executable to compare against database", "Name: "); + URL url = BSimClientFactory.deriveBSimURL(urlString); try (FunctionDatabase database = BSimClientFactory.buildClient(url, true)) { - // FileScoreCaching cache = new FileScoreCaching("/tmp/test_scorecacher.txt"); - TableScoreCaching cache = new TableScoreCaching(database); - exeCompare = new ExecutableComparison(database, 1000000, - "11111111111111111111111111111111", cache, monitor); - // Specify the list of executables to compare by giving their md5 hash + QueryExeInfo exeInfo = new QueryExeInfo(); + exeInfo.filterExeName = execName; + ResponseExe exeResult = exeInfo.execute(database); + if (exeResult == null) { + String message = database.getLastError() != null ? database.getLastError().message + : "Unrecoverable error"; + throw new IOException(message); + } + else if (exeResult.recordCount == 0) { + throw new InvalidParameterException( + "Executable " + execName + " is not present in database"); + } + else if (exeResult.recordCount > 1) { + println("Multiple executables with the name - " + execName); + ExecutableRecord exeRecord = exeResult.records.get(0); + print("Using "); + println(exeRecord.printRaw()); + } + String baseMd5 = exeResult.records.get(0).getMd5(); + + ScoreCaching cache = null; // If null, self scores will not be cached + + // Scores can be cached in the local file system by using FileScoreCaching + // cache = new FileScoreCaching("/tmp/test_scorecacher.txt"); + + // Scores can be cached in a dedicated table within the database by using TableScoreCaching + // TableScoreCaching is currently only supported for the PostgreSQL back-end. + // cache = new TableScoreCaching(database); + + exeCompare = new ExecutableComparison(database, 1000000, baseMd5, cache, monitor); + // Its possible to specify the executables to compare with the base executable by + // specifying their md5 hashes directly. // exeCompare.addExecutable("22222222222222222222222222222222"); // 32 hex-digit string // exeCompare.addExecutable("33333333333333333333333333333333"); + + // Otherwise specify that we should compare the base executable against all executables exeCompare.addAllExecutables(5000); ExecutableScorer scorer = exeCompare.getScorer(); if (!exeCompare.isConfigured()) { diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/tables/OptionalTable.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/tables/OptionalTable.java index 41b57cd5e3..c1b8c1df72 100755 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/tables/OptionalTable.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/tables/OptionalTable.java @@ -27,14 +27,15 @@ import java.sql.*; */ public class OptionalTable { - private final String TABLE_EXISTS_STMT = "SELECT schemaname FROM pg_tables where tablename='#'"; - private final String GRANT_STMT = "GRANT SELECT ON # TO PUBLIC"; - private final String DELETE_ALL_STMT = "DELETE FROM #"; + private String TABLE_EXISTS_STMT = "SELECT schemaname FROM pg_tables where tablename='#'"; + private String GRANT_STMT = "GRANT SELECT ON # TO PUBLIC"; + private String DELETE_ALL_STMT = "DELETE FROM #"; - private final String INSERT_STMT = "INSERT INTO # (key,value) VALUES(?,?)"; - private final String UPDATE_STMT = "UPDATE # SET value = ? WHERE key = ?"; - private final String SELECT_STMT = "SELECT value FROM # WHERE key = ?"; - private final String DELETE_STMT = "DELETE FROM # WHERE key = ?"; + private String INSERT_STMT = "INSERT INTO # (key,value) VALUES(?,?)"; + private String UPDATE_STMT = "UPDATE # SET value = ? WHERE key = ?"; + private String SELECT_STMT = "SELECT value FROM # WHERE key = ?"; + private String DELETE_STMT = "DELETE FROM # WHERE key = ?"; + private String LOCK_STMT = "LOCK TABLE # IN SHARE ROW EXCLUSIVE MODE"; private Connection db = null; // Connection to the database @@ -43,7 +44,6 @@ public class OptionalTable { private final CachedStatement selectStatement = new CachedStatement<>(); private final CachedStatement deleteStatement = new CachedStatement<>(); private final CachedStatement reusableStatement = new CachedStatement<>(); - private final String lockString; private String name = null; // name of the table private int keyType = -1; // Type of the key column @@ -61,7 +61,14 @@ public class OptionalTable { keyType = kType; valueType = vType; db = d; - lockString = "LOCK TABLE " + nm + " IN SHARE ROW EXCLUSIVE MODE"; + TABLE_EXISTS_STMT = generateSQLCommand(TABLE_EXISTS_STMT, nm); + GRANT_STMT = generateSQLCommand(GRANT_STMT, nm); + DELETE_ALL_STMT = generateSQLCommand(DELETE_ALL_STMT, nm); + INSERT_STMT = generateSQLCommand(INSERT_STMT, nm); + UPDATE_STMT = generateSQLCommand(UPDATE_STMT, nm); + SELECT_STMT = generateSQLCommand(SELECT_STMT, nm); + DELETE_STMT = generateSQLCommand(DELETE_STMT, nm); + LOCK_STMT = generateSQLCommand(LOCK_STMT, nm); } private Statement getReusableStatement() throws SQLException { @@ -73,7 +80,7 @@ public class OptionalTable { * @throws SQLException if the server reports an error */ public void lockForWrite() throws SQLException { - getReusableStatement().execute(lockString); + getReusableStatement().execute(LOCK_STMT); } /** @@ -98,7 +105,7 @@ public class OptionalTable { * @param ptr is the string to be modified * @return the modified string */ - private String generateSQLCommand(String ptr) { + private static String generateSQLCommand(String ptr, String name) { int first = ptr.indexOf('#'); int second = ptr.indexOf('#', first + 1); String res; @@ -160,8 +167,7 @@ public class OptionalTable { String sqlstring = buffer.toString(); Statement st = getReusableStatement(); st.executeUpdate(sqlstring); - sqlstring = generateSQLCommand(GRANT_STMT); - st.executeUpdate(sqlstring); + st.executeUpdate(GRANT_STMT); } /** @@ -169,8 +175,7 @@ public class OptionalTable { * @throws SQLException for problems with the connection */ public void clearTable() throws SQLException { - String sqlString = generateSQLCommand(DELETE_ALL_STMT); - getReusableStatement().executeUpdate(sqlString); + getReusableStatement().executeUpdate(DELETE_ALL_STMT); } /** @@ -179,9 +184,8 @@ public class OptionalTable { * @throws SQLException for problems with the connection */ public boolean exists() throws SQLException { - String sqlString = generateSQLCommand(TABLE_EXISTS_STMT); boolean result = false; - try (ResultSet rs = getReusableStatement().executeQuery(sqlString)) { + try (ResultSet rs = getReusableStatement().executeQuery(TABLE_EXISTS_STMT)) { if (rs.next()) { result = rs.getString(1).equals("public"); }