diff --git a/Ghidra/Features/Base/ghidra_scripts/FixOldSTVariableStorageScript.java b/Ghidra/Features/Base/ghidra_scripts/FixOldSTVariableStorageScript.java new file mode 100644 index 0000000000..f6256dc510 --- /dev/null +++ b/Ghidra/Features/Base/ghidra_scripts/FixOldSTVariableStorageScript.java @@ -0,0 +1,174 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// This fixup script is intended to be run against x86 programs created prior +// to Ghidra 10.0.3 to update old ST0..ST7 floating point register +// locations assigned to function parameters and local variables. The +// address assignment for these registers was changed with Ghidra 10.0.3 +// x86 slaspec change (GP-1228). +// +// This script can be run multiple times without harm +//@category Functions +import java.util.Set; + +import ghidra.app.script.GhidraScript; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.InvalidInputException; + +public class FixOldSTVariableStorageScript extends GhidraScript { + + private static final int ST_SIZE = 10; + + // See Ghidra/Processors/x86/data/languages/ia.sinc + private static final long OLD_ST_BASE_OFFSET = 0x1000; + private static final long OLD_ST_OFFSET_SPACING = ST_SIZE; // each offset 10-bytes from the previous + + // Must query new ST0 address since it may change again + private static final long NEW_ST_OFFSET_SPACING = 16; // each offset 16-bytes from the previous + + @Override + protected void run() throws Exception { + + if (currentProgram == null || !"x86".equals(currentProgram.getLanguage().getProcessor().toString())) { + popup("Script supports x86 programs only"); + return; + } + + // Spot check new ST0 placement + Register st0 = currentProgram.getRegister("ST0"); + Register st1 = currentProgram.getRegister("ST1"); + if (st0 == null || st1 == null) { + popup("Unsupported x86 language"); + return; + } + + long st0Offset = st0.getAddress().getOffset(); + long st1Offset = st1.getAddress().getOffset(); + if (st0Offset == OLD_ST_BASE_OFFSET || (st1Offset - st0Offset) != NEW_ST_OFFSET_SPACING) { + popup("Unsupported x86 ST register placement"); + return; + } + + STRegisterFixup stRegisterFixup = new STRegisterFixup(st0Offset); + int count = 0; + + SymbolTable symbolTable = currentProgram.getSymbolTable(); + for (Symbol s : symbolTable.getDefinedSymbols()) { + SymbolType type = s.getSymbolType(); + if (type == SymbolType.PARAMETER || type == SymbolType.LOCAL_VAR) { + if (stRegisterFixup.fixupVariableStorage((Variable) s.getObject())) { + ++count; + } + } + else if (type == SymbolType.FUNCTION) { + Function function = (Function) s.getObject(); + if (stRegisterFixup.fixupVariableStorage(function.getReturn())) { + ++count; + } + } + } + + if (count != 0) { + popup("Fixed " + count + " ST register uses"); + } + else { + popup("No old ST register uses were found"); + } + } + + private class STRegisterFixup { + + private Set oldSTVarnodes; // ST0..ST7 + private long newStBaseOffset; + + STRegisterFixup(long newStBaseOffset) { + this.newStBaseOffset = newStBaseOffset; + + AddressSpace registerSpace = currentProgram.getAddressFactory().getRegisterSpace(); + + long oldSTBaseOffset = OLD_ST_BASE_OFFSET; + oldSTVarnodes = Set.of( + new Varnode(registerSpace.getAddress(oldSTBaseOffset), + ST_SIZE), // Old ST0 at 0x1000, now at 0x1106 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + OLD_ST_OFFSET_SPACING), + ST_SIZE), // Old ST1 at 0x100a, now at 0x1116 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + (2 * OLD_ST_OFFSET_SPACING)), + ST_SIZE), // Old ST2 at 0x1014, now at 0x1126 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + (3 * OLD_ST_OFFSET_SPACING)), + ST_SIZE), // Old ST3 at 0x101e, now at 0x1136 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + (4 * OLD_ST_OFFSET_SPACING)), + ST_SIZE), // Old ST4 at 0x1028, now at 0x1146 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + (5 * OLD_ST_OFFSET_SPACING)), + ST_SIZE), // Old ST5 at 0x1032, now at 0x1156 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + (6 * OLD_ST_OFFSET_SPACING)), + ST_SIZE), // Old ST6 at 0x103c, now at 0x1166 + new Varnode(registerSpace.getAddress(oldSTBaseOffset + (7 * OLD_ST_OFFSET_SPACING)), + ST_SIZE) // Old ST7 at 0x1046*, now at 0x1176 // Old ST7 at 0x1046, now at 0x1176 + ); + + } + + private boolean fixupVariableStorage(Variable var) { + VariableStorage varStore = var.getVariableStorage(); + Varnode[] varnodes = varStore.getVarnodes(); + if (fixupStorageVarnodes(varnodes)) { + try { + var.setDataType(var.getDataType(), + new VariableStorage(currentProgram, varnodes), false, var.getSource()); + } + catch (InvalidInputException e) { + throw new AssertionError("Unexpected error for ST register varnode assignment", + e); + } + return true; + } + return false; + } + + private boolean fixupStorageVarnodes(Varnode[] varnodes) { + boolean hasFixup = false; + for (int i = 0; i < varnodes.length; i++) { + Varnode v = getReplacement(varnodes[i]); + if (v != null) { + hasFixup = true; + varnodes[i] = v; + } + } + return hasFixup; + } + + Varnode getReplacement(Varnode v) { + if (!oldSTVarnodes.contains(v)) { + return null; + } + Address regAddr = v.getAddress(); + long stOffset = regAddr.getOffset() - OLD_ST_BASE_OFFSET; + long stIndex = stOffset / OLD_ST_OFFSET_SPACING; + + // Form updated ST varnode + stOffset = newStBaseOffset + (stIndex * NEW_ST_OFFSET_SPACING); + return new Varnode(regAddr.getNewAddress(stOffset), 10); + } + + + } + +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java index 5845a0900f..d802424fea 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectLocator.java @@ -17,6 +17,7 @@ package ghidra.framework.model; import java.io.File; import java.net.URL; +import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -37,16 +38,30 @@ public class ProjectLocator { private URL url; + /** + * Set of characters specifically disallowed in project name or path. + * These characters may interfere with path and URL parsing. + */ + public static Set DISALLOWED_CHARS = Set.of(':', ';', '&', '?', '#'); + /** * Construct a project locator object. - * @param path path to parent directory (may or may not exist). The user's temp directory - * will be used if this value is null or blank. + * @param path absolute path to parent directory (may or may not exist). The user's temp directory + * will be used if this value is null or blank. The use of "\" characters will always be replaced + * with "/". * WARNING: Use of a relative paths should be avoided (e.g., on a windows platform - * an absolute path should start with a drive letter specification such as C:\path, - * while this same path on a Linux platform would be treated as relative). - * @param name name of the project + * an absolute path should start with a drive letter specification such as C:\path). + * A path such as "/path" on windows will utilize the current default drive and will + * not throw an exception. If a drive letter is specified it must specify an absolute + * path (e.g., C:\, C:\path). + * @param name name of the project (may only contain alphanumeric characters or + * @throws IllegalArgumentException if an absolute path is not specified or invalid project name */ public ProjectLocator(String path, String name) { + if (name.contains("/") || name.contains("\\")) { + throw new IllegalArgumentException("name contains path separator character: " + name); + } + checkInvalidChar("name", name, 0); if (name.endsWith(PROJECT_FILE_SUFFIX)) { name = name.substring(0, name.length() - PROJECT_FILE_SUFFIX.length()); } @@ -59,25 +74,75 @@ public class ProjectLocator { } /** - * Ensure that absolute path is specified. + * Check for characters explicitly disallowed in path or project name. + * @param type type of string to include in exception + * @param str string to check + * @param startIndex index at which to start checking + * @throws IllegalArgumentException if str contains invalid character + */ + private static void checkInvalidChar(String type, String str, int startIndex) { + for (int i = startIndex; i < str.length(); i++) { + char c = str.charAt(i); + if (DISALLOWED_CHARS.contains(c)) { + throw new IllegalArgumentException( + type + " contains invalid character: '" + c + "'"); + } + } + } + + /** + * Ensure that absolute path is specified and normalize its format. + * An absolute path may start with a windows drive letter (e.g., c:/a/b, /c:/a/b) + * or without (e.g., /a/b). Although for Windows the lack of a drive letter is + * not absolute, for consistency with Linux we permit this form which on + * Windows will use the default drive for the process. The resulting path + * may be transormed to always end with a "/" and if started with a drive letter + * (e.g., "c:/") it will have a "/" prepended (e.g., "/c:/", both forms + * are treated the same by the {@link File} class under Windows). * @param path path to be checked and possibly modified. * @return path to be used + * @throws IllegalArgumentException if an invalid path is specified */ private static String checkAbsolutePath(String path) { - if (path.startsWith("/") && path.length() >= 4 && path.indexOf(":/") == 2 && - Character.isLetter(path.charAt(1))) { - // strip leading "/" on Windows paths (e.g., /C:/mydir) and transform separators to '\' - path = path.substring(1); - path = path.replace('/', '\\'); + int scanIndex = 0; + path = path.replace('\\', '/'); + int len = path.length(); + if (!path.startsWith("/")) { + // Allow paths to start with windows drive letter (e.g., c:/a/b) + if (len >= 3 && hasAbsoluteDriveLetter(path, 0)) { + path = "/" + path; + } + else { + throw new IllegalArgumentException("absolute path required"); + } + scanIndex = 3; } - if (path.endsWith("/") || path.endsWith("\\")) { - path = path.substring(0, path.length() - 1); + else if (len >= 3 && hasDriveLetter(path, 1)) { + if (len < 4 || path.charAt(3) != '/') { + // path such as "/c:" not permitted + throw new IllegalArgumentException("absolute path required"); + } + scanIndex = 4; + } + checkInvalidChar("path", path, scanIndex); + if (!path.endsWith(File.separator)) { + path += File.separator; } return path; } + private static boolean hasDriveLetter(String path, int index) { + return Character.isLetter(path.charAt(index++)) && path.charAt(index) == ':'; + } + + private static boolean hasAbsoluteDriveLetter(String path, int index) { + int pathIndex = index + 2; + return path.length() > pathIndex && hasDriveLetter(path, index) && + path.charAt(pathIndex) == '/'; + } + /** - * Returns true if this project URL corresponds to a transient project + * @returns true if this project URL corresponds to a transient project * (e.g., corresponds to remote Ghidra URL) */ public boolean isTransient() { @@ -85,7 +150,7 @@ public class ProjectLocator { } /** - * Returns the URL associated with this local project. If using a temporary transient + * @returns the URL associated with this local project. If using a temporary transient * project location this URL should not be used. */ public URL getURL() { @@ -93,7 +158,7 @@ public class ProjectLocator { } /** - * Get the name of the project identified by this project info. + * @returns the name of the project identified by this project info. */ public String getName() { return name; @@ -110,21 +175,21 @@ public class ProjectLocator { } /** - * Returns the project directory + * @returns the project directory */ public File getProjectDir() { return new File(location, name + PROJECT_DIR_SUFFIX); } /** - * Returns the file that indicates a Ghidra project. + * @returns the file that indicates a Ghidra project. */ public File getMarkerFile() { return new File(location, name + PROJECT_FILE_SUFFIX); } /** - * Returns project lock file to prevent multiple accesses to the + * @returns project lock file to prevent multiple accesses to the * same project at once. */ public File getProjectLockFile() { @@ -132,7 +197,7 @@ public class ProjectLocator { } /** - * Returns the project directory file extension. + * @returns the project directory file extension. */ public static String getProjectDirExtension() { return PROJECT_DIR_SUFFIX; @@ -164,7 +229,7 @@ public class ProjectLocator { } /** - * Returns the file extension suitable for creating file filters for the file chooser. + * @returns the file extension suitable for creating file filters for the file chooser. */ public static String getProjectExtension() { return PROJECT_FILE_SUFFIX; @@ -180,7 +245,7 @@ public class ProjectLocator { } /** - * Returns true if project storage exists + * @returns true if project storage exists */ public boolean exists() { return getMarkerFile().isFile() && getProjectDir().isDirectory(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java index 08837d4e77..bdf93627de 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java @@ -15,6 +15,7 @@ */ package ghidra.framework.protocol.ghidra; +import java.io.File; import java.net.*; import java.util.Objects; import java.util.regex.Pattern; @@ -33,6 +34,8 @@ import ghidra.framework.remote.GhidraServerHandle; */ public class GhidraURL { + // TODO: URL encoding/decoding should be used + public static final String PROTOCOL = "ghidra"; private static final String PROTOCOL_URL_START = PROTOCOL + ":/"; @@ -216,24 +219,67 @@ public class GhidraURL { } /** - * Ensure that absolute path is specified. Any use of Windows - * separator (back-slash) will be converted to a forward-slash. + * Ensure that absolute path is specified and normalize its format. + * An absolute path may start with a windows drive letter (e.g., c:/a/b, /c:/a/b) + * or without (e.g., /a/b). Although for Windows the lack of a drive letter is + * not absolute, for consistency with Linux we permit this form which on + * Windows will use the default drive for the process. If path starts with a drive + * letter (e.g., "c:/") it will have a "/" prepended (e.g., "/c:/", both forms + * are treated the same by the {@link File} class under Windows). * @param path path to be checked and possibly modified. * @return path to be used + * @throws IllegalArgumentException if an invalid path is specified */ private static String checkAbsolutePath(String path) { + int scanIndex = 0; path = path.replace('\\', '/'); + int len = path.length(); if (!path.startsWith("/")) { - if (path.length() >= 3 && path.indexOf(":/") == 1 && - Character.isLetter(path.charAt(0))) { - // prepend a "/" on Windows paths (e.g., C:/mydir) + // Allow paths to start with windows drive letter (e.g., c:/a/b) + if (len >= 3 && hasAbsoluteDriveLetter(path, 0)) { path = "/" + path; } - else { // absence of drive letter is tolerated even if not absolute on windows - throw new IllegalArgumentException("Absolute directory path required"); + else { + throw new IllegalArgumentException("absolute path required"); + } + scanIndex = 3; + } + else if (len >= 3 && hasDriveLetter(path, 1)) { + if (len < 4 || path.charAt(3) != '/') { + // path such as "/c:" not permitted + throw new IllegalArgumentException("absolute path required"); + } + scanIndex = 4; + } + checkInvalidChar("path", path, scanIndex); + return path; + } + + private static boolean hasDriveLetter(String path, int index) { + return Character.isLetter(path.charAt(index++)) && path.charAt(index) == ':'; + } + + private static boolean hasAbsoluteDriveLetter(String path, int index) { + int pathIndex = index + 2; + return path.length() > pathIndex && hasDriveLetter(path, index) && + path.charAt(pathIndex) == '/'; + } + + /** + * Check for characters explicitly disallowed in path or project name. + * @param type type of string to include in exception + * @param str string to check + * @param startIndex index at which to start checking + * @throws IllegalArgumentException if str contains invalid character + */ + private static void checkInvalidChar(String type, String str, int startIndex) { + for (int i = startIndex; i < str.length(); i++) { + char c = str.charAt(i); + if (ProjectLocator.DISALLOWED_CHARS.contains(c)) { + throw new IllegalArgumentException( + type + " contains invalid character: '" + c + "'"); } } - return path; } /** @@ -256,8 +302,15 @@ public class GhidraURL { throw new IllegalArgumentException("Unsupported query/ref used with project path"); } projectPathOrURL = checkAbsolutePath(projectPathOrURL); - String[] splitName = splitOffName(projectPathOrURL); - return makeURL(splitName[0], splitName[1]); + int minSplitIndex = projectPathOrURL.charAt(2) == ':' ? 3 : 0; + int splitIndex = projectPathOrURL.lastIndexOf('/'); + if (splitIndex < minSplitIndex || projectPathOrURL.length() == (splitIndex + 1)) { + throw new IllegalArgumentException("Absolute project path is missing project name"); + } + ++splitIndex; + String location = projectPathOrURL.substring(0, splitIndex); + String projectName = projectPathOrURL.substring(splitIndex); + return makeURL(location, projectName); } try { return new URL(projectPathOrURL); @@ -467,11 +520,12 @@ public class GhidraURL { * @param projectFilePath file path (e.g., /a/b/c, may be null) * @param ref location reference (may be null) * @return local Ghidra project URL + * @throws IllegalArgumentException if an absolute projectLocation path is not specified */ public static URL makeURL(String projectLocation, String projectName, String projectFilePath, String ref) { if (StringUtils.isBlank(projectLocation) || StringUtils.isBlank(projectName)) { - throw new IllegalArgumentException("Inavlid project location and/or name"); + throw new IllegalArgumentException("Invalid project location and/or name"); } String path = checkAbsolutePath(projectLocation); if (!path.endsWith("/")) { diff --git a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java new file mode 100644 index 0000000000..f37194ba33 --- /dev/null +++ b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/ProjectLocatorTest.java @@ -0,0 +1,182 @@ +/* ### + * 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.framework.model; + +import static org.junit.Assert.*; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.framework.OperatingSystem; +import ghidra.framework.protocol.ghidra.Handler; + +public class ProjectLocatorTest extends AbstractGenericTest { + + @Before + public void setUp() { + Handler.registerHandler(); + } + + // + // Behavior of test differs when run on Windows vs Linux/Mac + // + + @Test + public void testPaths() throws MalformedURLException { + + ProjectLocator pl = new ProjectLocator("c:\\", "bob"); + assertEquals(new URL("ghidra:/c:/bob"), pl.getURL()); + assertEquals("/c:/", pl.getLocation()); + assertEquals(new File("/c:/bob.rep"), pl.getProjectDir()); + assertEquals(new File("/c:/bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + assertEquals("c:/bob.rep", pl.getProjectDir().getAbsolutePath()); + assertEquals("c:/bob.gpr", pl.getMarkerFile().getAbsolutePath()); + } + + pl = new ProjectLocator("/c:/", "bob"); + assertEquals(new URL("ghidra:/c:/bob"), pl.getURL()); + assertEquals("/c:/", pl.getLocation()); + assertEquals(new File("/c:/bob.rep"), pl.getProjectDir()); + assertEquals(new File("/c:/bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + assertEquals("c:/bob.rep", pl.getProjectDir().getAbsolutePath()); + assertEquals("c:/bob.gpr", pl.getMarkerFile().getAbsolutePath()); + } + + pl = new ProjectLocator("c:\\a", "bob"); + assertEquals(new URL("ghidra:/c:/a/bob"), pl.getURL()); + assertEquals("/c:/a/", pl.getLocation()); + assertEquals(new File("/c:/a/bob.rep"), pl.getProjectDir()); + assertEquals(new File("/c:/a/bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + assertEquals("c:/a/bob.rep", pl.getProjectDir().getAbsolutePath()); + assertEquals("c:/a/bob.gpr", pl.getMarkerFile().getAbsolutePath()); + } + + pl = new ProjectLocator("c:\\a\\", "bob"); + assertEquals(new URL("ghidra:/c:/a/bob"), pl.getURL()); + assertEquals("/c:/a/", pl.getLocation()); + assertEquals(new File("/c:/a/bob.rep"), pl.getProjectDir()); + assertEquals(new File("/c:/a/bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + assertEquals("c:/a/bob.rep", pl.getProjectDir().getAbsolutePath()); + assertEquals("c:/a/bob.gpr", pl.getMarkerFile().getAbsolutePath()); + } + + pl = new ProjectLocator("\\", "bob"); + assertEquals(new URL("ghidra:/bob"), pl.getURL()); + assertEquals("/", pl.getLocation()); + assertEquals(new File("/bob.rep"), pl.getProjectDir()); + assertEquals(new File("/bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + // NOTE: Sensitive to default drive for process + assertEquals("c:/bob.rep", pl.getProjectDir().getAbsolutePath()); + assertEquals("c:/bob.gpr", pl.getMarkerFile().getAbsolutePath()); + } + + pl = new ProjectLocator("\\a\\", "bob"); + assertEquals(new URL("ghidra:/a/bob"), pl.getURL()); + assertEquals("/a/", pl.getLocation()); + assertEquals(new File("/a/bob.rep"), pl.getProjectDir()); + assertEquals(new File("/a/bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + if (OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS) { + // NOTE: Sensitive to default drive for process + assertEquals("c:/a/bob.rep", pl.getProjectDir().getAbsolutePath()); + assertEquals("c:/a/bob.gpr", pl.getMarkerFile().getAbsolutePath()); + } + } + + @Test + public void testTempPath() throws MalformedURLException { + + String tmpPath = System.getProperty("java.io.tmpdir").replace("\\", "/"); + if (!tmpPath.startsWith("/")) { + tmpPath = "/" + tmpPath; + } + if (!tmpPath.endsWith("/")) { + tmpPath += "/"; + } + + ProjectLocator pl = new ProjectLocator("", "bob"); + assertEquals(tmpPath, pl.getLocation()); + assertEquals(new URL("ghidra:" + tmpPath + "bob"), pl.getURL()); + assertEquals(new File(pl.getLocation() + "bob.rep"), pl.getProjectDir()); + assertEquals(new File(pl.getLocation() + "bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + + pl = new ProjectLocator(null, "bob"); + assertEquals(tmpPath, pl.getLocation()); + assertEquals(new URL("ghidra:" + tmpPath + "bob"), pl.getURL()); + assertEquals(new File(pl.getLocation() + "bob.rep"), pl.getProjectDir()); + assertEquals(new File(pl.getLocation() + "bob.gpr"), pl.getMarkerFile()); + assertEquals("bob", pl.getName()); + } + + @Test + public void testBadPaths() { + + // relative paths + doTestBadPath("c:", "bob"); + doTestBadPath("/c:", "bob"); + doTestBadPath("a/b", "bob"); + + // bad paths chars + doTestBadPath("/a?/b", "bob"); + doTestBadPath("/a#/b", "bob"); + doTestBadPath("/a/:b", "bob"); + doTestBadPath("/a;/b", "bob"); + doTestBadPath("/a&/b", "bob"); + + // bad name chars + doTestBadPath("/a/b", "b?ob"); + doTestBadPath("/a/b", "b#ob"); + doTestBadPath("/a/b", "b:ob"); + doTestBadPath("/a/b", "b;ob"); + doTestBadPath("/a/b", "b?ob"); + doTestBadPath("/a/b", "b&ob"); + doTestBadPath("/a/b", "b/ob"); + doTestBadPath("/a/b", "b\\ob"); + + } + + private void doTestBadPath(String path, String name) { + try { + new ProjectLocator(path, name); + fail("expected absolute path error"); + } + catch (IllegalArgumentException e) { + // expected + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionDBUtil.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionDBUtil.java index c85958659e..682cac0430 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionDBUtil.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/HighFunctionDBUtil.java @@ -793,8 +793,21 @@ public class HighFunctionDBUtil { boolean nameCollision = false; Variable[] localVariables = function.getLocalVariables(VariableFilter.UNIQUE_VARIABLE_FILTER); + // Clean out any facet symbols with bad data-types + for (int i = 0; i < localVariables.length; ++i) { + Variable var = localVariables[i]; + if (var.getName().startsWith(UnionFacetSymbol.BASENAME)) { + if (!UnionFacetSymbol.isUnionType(var.getDataType())) { + function.removeVariable(var); + localVariables[i] = null; + } + } + } Variable preexistingVar = null; for (Variable var : localVariables) { + if (var == null) { + continue; + } if (var.getFirstUseOffset() == firstUseOffset && var.getFirstStorageVarnode().getOffset() == hash) { preexistingVar = var; @@ -807,10 +820,12 @@ public class HighFunctionDBUtil { symbolName = symbolName + '_' + Integer.toHexString(DynamicHash.getComparable(hash)); } if (preexistingVar != null) { - if (preexistingVar.getName().equals(symbolName)) { - return; // No change to make + if (!preexistingVar.getName().equals(symbolName)) { + preexistingVar.setName(symbolName, source); // Change the name + } + if (!preexistingVar.getDataType().equals(dt)) { + preexistingVar.setDataType(dt, source); } - preexistingVar.setName(symbolName, source); // Change the name return; } Program program = function.getProgram(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java index 417e1e26d1..c4179411f2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/LocalSymbolMap.java @@ -453,7 +453,7 @@ public class LocalSymbolMap { id = getNextId(); } HighSymbol sym; - if (DynamicHash.getMethodFromHash(hash) > 3) { + if (DynamicHash.getMethodFromHash(hash) > 3 && UnionFacetSymbol.isUnionType(dt)) { int fieldNum = UnionFacetSymbol.extractFieldNumber(nm); sym = new UnionFacetSymbol(id, nm, dt, fieldNum, func); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/UnionFacetSymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/UnionFacetSymbol.java index f9a665a230..884ca384f2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/UnionFacetSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/UnionFacetSymbol.java @@ -21,7 +21,7 @@ import static ghidra.program.model.pcode.ElementId.*; import java.io.IOException; import ghidra.program.model.address.Address; -import ghidra.program.model.data.DataType; +import ghidra.program.model.data.*; /** * A specialized HighSymbol that directs the decompiler to use a specific field of a union, @@ -80,4 +80,26 @@ public class UnionFacetSymbol extends HighSymbol { } return Integer.decode(nm.substring(pos + BASENAME.length(), endpos)) - 1; } + + /** + * Return true if the given data-type is either a union or a pointer to a union + * and is suitable for being the data-type of UnionFacetSymbol + * @param dt is the given data-type + * @return true if the data-type is a union or a pointer to a union + */ + public static boolean isUnionType(DataType dt) { + if (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + } + if (dt instanceof Pointer) { + dt = ((Pointer) dt).getDataType(); + if (dt == null) { + return false; + } + if (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + } + } + return (dt instanceof Union); + } }