diff --git a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md
index a0814cb4f76..8e83034bedd 100644
--- a/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md
+++ b/Ghidra/Configurations/Public_Release/src/global/docs/ChangeHistory.md
@@ -1,3 +1,82 @@
+# Ghidra 12.1.3 Change History (August 2026)
+
+### Improvements
+* _Basic Infrastructure_. Added stricter filtering to characters used for symbol names. (GP-6843, Issue #9165, #9172, #9206)
+* _BSim_. Improved escaping in `BSimControlLaunchable`. (GP-6977)
+* _BSim_. Upgraded the PostgreSQL JDBC Driver from 42.7.11 to 42.7.13. (GP-7105)
+* _BSim_. Upgraded postgresql for BSim to 15.18. (GP-7107)
+* _Demangler_. The Swift Demangler Analyzer no longer accepts a user-specified path to the Swift binary directory. Instead, the Demangler expects Swift to be on the `PATH`. (GP-7045)
+* _Exporter_. Added packed Debugger Trace import/export using `*.gzt` files. (GP-6891)
+* _GUI_. Updated the Headless Analyzer to not write to the user's preferences. (GP-7096, Issue #9414)
+* _Importer:COFF_. Implemented the COFF `IMAGE_REL_ARM64_ADDR64` relocation. (GP-7088, Issue #9408)
+* _Importer:Mach-O_. Provided safer parsing of `dyld_shared_cache` files. (GP-7046)
+* _Logging_. Upgraded log4j to version 2.26.1. (GP-7134)
+* _Multi-User_. Corrected potential network performance issue related to Ghidra Server use. Full benefit requires update of both Ghidra Server installation and Ghidra client software. (GP-7012, Issue #2752, #9316)
+* _Processors_. Made SparcV9 register display formatting more consistent. (GP-941)
+* _Scripting_. Updated the Structure Editor to perform a structure reload when the structure's description is changed outside of the editor. (GP-7118, Issue #9461)
+* _Search_. Updated __Find Uses of__ for FunctionDefinitions to find references inside of functions and function parameters. (GP-7044, Issue #9342)
+
+### Bugs
+* _Analysis_. Fixed failure to recover switch instructions if the switch is computed with an instruction with internal pcode branching such as the AARCH64 `csel` instruction. (GP-7023)
+* _BSim_. Improved `GhidraURL.toURL` to handle Ghidra URLs previously stored by BSim where proper URL encoding was never performed. (GP-6970, Issue #9275)
+* _Data Types_. Corrected BuiltIn GUID datatype alignment to be 4 bytes instead of 8 bytes. Packed structures containing this datatype will need to be re-packed to pick up on this change. (GP-6953, Issue #9200)
+* _DB_. Corrected Ghidra database problem which could prevent analysis times from getting properly saved to a program. (GP-7070, Issue #9346)
+* _DB_. Corrected internal method behavior for `ContentHandler.getChangeSet` which could return an empty change set if the version range does not include the latest version. (GP-7113, Issue #9415)
+* _Debugger:Agents_. Now applying proper argument quoting and metacharacter escaping to prevent `ShellUtils` command injection. (GP-6934, Issue #9283)
+* _Debugger:Time_. Fixed PC column in Time panel when emulating from a live target. (GP-6841)
+* _Debugger:Trace_. Fixed a potential OOM exception in `ObjectStorageStreamAdapter` used by Trace database. (GP-6886)
+* _Decompiler_. Added Decompiler support for memory accesses that wrap from high to low addresses. (GP-6936)
+* _FileSystems_. Fixed a potential path-traversal vulnerability in `AndroidProjectCreator`. (GP-6885)
+* _GUI_. Fixed bugs in the Column Filter Dialog that caused `NullPointerExceptions` on some Mac systems. (GP-7050)
+* _GUI_. Fixed a deadlock seen when saving a program. (GP-7051, Issue #9358)
+* _GUI_. Fixed incorrect tool tips in the File Chooser that are sometimes seen when filenames are clipped. (GP-7094)
+* _Importer_. Fixed a potential path-traversal vulnerability when importing XML `*.bytes` files. (GP-7064)
+* _Importer_. Fixed a potential infinite loop in the PEF loader and made improvements to OMF parsing. (GP-7071)
+* _Importer_. Providing more efficient parsing of System Object Model (SOM) binaries. (GP-7123)
+* _Importer_. Providing more efficient parsing of Motorola Hex files. (GP-7124)
+* _Importer:ELF_. Corrected an issue found with ELF dynamic PLTGOT processing, in the absence of section headers, which could cause the last PLT to be skipped. (GP-7056)
+* _Importer:ELF_. Corrected ELF Importer regression with Ghidra 12.1.2 which could fail when processing dynamic GNU Hashtable and symbol table. (GP-7061, Issue #9369)
+* _Importer:Mach-O_. Fixed an `IndexOutOfBoundsException` in the Mach-O loader that could occur when importing a binary with a `LC_DYSYMTAB` load command but having a missing or empty `LC_SYMTAB` load command. (GP-7079, Issue #9399)
+* _Importer:PE_. Fixed unsigned PE export ordinal-table indexes. (GP-7085, Issue #9396, #9397)
+* _Importer:PE_. Improved the handling of JDOM attribute errors in the PE `LibraryLookupTable`. (GP-7087, Issue #6288)
+* _Listing_. Fixed an `IllegalArgumentException` that occurred when trying to perform __Copy Special__ in the Listing with a program name that contains the exclamation mark (`!`) character. (GP-7055, Issue #9362)
+* _PDB_. Fixed a memory-consumption issue that could occur with a malformed PDB directory stream. (GP-6971)
+* _Processors_. Added support for RH850G3 instructions in the V850 language module. (GP-2907, Issue #4788)
+* _Processors_. Fixed register operand for ARM M-profile `msr apsr` instruction operand encoding. (GP-5206, Issue #6597, #6598)
+* _Processors_. Added several missing x86 instructions. (GP-5780)
+* _Processors_. Added semantics for several x86 AVX instructions. (GP-6061, Issue #9184, #9197)
+* _Processors_. Added CPU32 variant for the M68000 processor module. (GP-6186, Issue #1244, #2617, #8463, #9084)
+* _Processors_. Corrected operand sizes for AARCH64 `stlrb` and `stlrh` instructions. (GP-6620, Issue #6468, #9043)
+* _Processors_. Corrected disassembly of HCS12 `BRN` instruction. (GP-6642, Issue #9001)
+* _Processors_. Fixed an issue with a missing return address for the SuperH4 `bsr` instruction. (GP-6699)
+* _Processors_. Corrected x86 logic for setting the `OF` flag for `SHR`, `RCL`, and `RCR` instructions. (GP-6767, Issue #9330)
+* _Processors_. Corrected Tricore `subc` carry flag usage. (GP-6769)
+* _Processors_. Corrected carry flag calculation for the MCS-96 `CMPL` instruction. (GP-6770)
+* _Processors_. Added missing x86 GFNI instructions. (GP-6818)
+* _Processors_. Corrected handling of RISC-V 16-bit operands. (GP-6876)
+* _Processors_. Fixed RISC-V emulation of `div` and `rem` instructions when divisor is 0. (GP-6909, Issue #6508, #9225, #9235)
+* _Processors_. Added PowerPC v3.0B, v3.0C instructions. (GP-6914)
+* _Processors_. Corrected interrupt mask size in ARM `mrs` for the `IPSR` system status register. (GP-6931, Issue #9251, #9252)
+* _Processors_. Fixed an issue with x86 legacy instructions using a `REX` prefix to read AVX-512 registers. (GP-6937, Issue #9256)
+* _Processors_. Added instruction index file for the SuperH processor. (GP-6964, Issue #9278)
+* _Processors_. Fixed stack parameter offset for e500mc default calling convention. (GP-6968, Issue #9089)
+* _Processors_. Fixed an issue with the M68000 `movem.w` instruction incrementing by 4 instead of 2. (GP-6983, Issue #8824)
+* _Processors_. Corrected x86 `rex chg eax, eax` to correctly disassemble to `nop`. (GP-7015, Issue #9327)
+* _Processors_. Corrected x86 `vpextrw` to correctly disassemble when `VEX.W=1`. (GP-7016, Issue #9323)
+* _Processors_. Corrected x86 `PINSRW`, `VPEXTRB`, and `VPEXTRD` intructions to use the correct masking of immediate byte for masks. (GP-7017, Issue #9328)
+* _Processors_. Corrected x86 `MOV` instruction to use 32-bit register operands when moving from segment registers. (GP-7018, Issue #9326)
+* _Processors_. Corrected x86 `pslld` instruction to use correct shift count for upper word. (GP-7019, Issue #9329)
+* _Processors_. Corrected AARCH64 NEON instructions which did not correctly zero-extend scalar results. (GP-7040, Issue #9277)
+* _Processors_. Corrected encoding for ARM `sev.w` instruction. (GP-7065, Issue #9371)
+* _Processors_. Corrected MIPS 16e language variants to use the correct `variant` tag in the ldefs file. (GP-7133, Issue #9432)
+* _Testing_. Fixed pcodetest framework to correctly honor the `build_exe` config option. (GP-7067, Issue #9374)
+* _Tests_. Patched the pcodetest builder to remove the use of `os.system()`. (GP-7007, Issue #9309)
+
+### Notable API Changes
+* _Debugger:Agents_. (GP-6934) `ShellUtils.generateLine` now requires a `Shell` argument, identifying the applicable shell. `ShellUtils.generateArgument` has been replaced by `Shell.generateArgument`.
+* _Decompiler_. (GP-6892) Added `Encoder.writeOpcode` method, specifically for opcode ordinals.
+* _Importer:ELF_. (GP-7057) Revised `ElfSymbol.isExternal() ` to disregard symbol type, `st_value`, and `st_size`, which were previously being considered.
+
# Ghidra 12.1.2 Change History (June 2026)
### Improvements
diff --git a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfServer.java b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfServer.java
index f60cd30bced..ccf8613a3c9 100644
--- a/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfServer.java
+++ b/Ghidra/Debug/Debugger-isf/src/main/java/ghidra/dbg/isf/IsfServer.java
@@ -37,7 +37,6 @@
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
-import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public class IsfServer extends Thread {
@@ -102,13 +101,13 @@ public DataTypeManager getDataTypeManager(String ns) {
try {
DataTypeManager dtm;
if (ns.endsWith(".gdt")) {
- dtm = openAsArchive(ns);
+ dtm = openAsDataTypeArchive(ns);
}
else if (ns.endsWith(".gzf")) {
- dtm = openAsDatabase(ns);
+ dtm = openAsProgramDatabase(ns);
}
else {
- dtm = openAsDomainFile(ns);
+ dtm = openAsProgramFile(ns);
}
managers.put(ns, dtm);
return dtm;
@@ -120,43 +119,43 @@ else if (ns.endsWith(".gzf")) {
}
}
- private DataTypeManager openAsDomainFile(String ns) throws Exception {
+ private DataTypeManager openAsProgramFile(String ns) throws Exception {
ProjectData projectData = project.getProjectData();
DomainFile df = projectData.getFile(ns);
+ if (!Program.class.isAssignableFrom(df.getDomainObjectClass())) {
+ throw new IOException("File does not correspond to Program content: " + ns);
+ }
+
+ // FIXME: Need to track and release Program instance after DTM use is complete (GP-6895)
Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
return program.getDataTypeManager();
}
- private DataTypeManager openAsArchive(String ns) throws Exception {
+ private DataTypeManager openAsDataTypeArchive(String ns) throws Exception {
File gdt = new File(ns);
return FileDataTypeManager.openFileArchive(gdt, false);
}
- private DataTypeManager openAsDatabase(String ns) throws Exception {
+ private DataTypeManager openAsProgramDatabase(String ns) throws Exception {
File gzf = new File(ns);
TaskMonitor dummy = TaskMonitor.DUMMY;
PackedDatabase db = PackedDatabase.getPackedDatabase(gzf, dummy);
+
DBHandle dbh = db.openForUpdate(dummy);
- ProgramDB p = null;
+
+ Program p;
+ boolean success = false;
try {
- p = new ProgramDB(dbh, OpenMode.UPDATE, dummy, this);
- }
- catch (VersionException e) {
- if (!e.isUpgradable()) {
- throw new RuntimeException(p + " uses an older version and is not upgradable.");
- }
+ p = new ProgramDB(dbh, OpenMode.UPGRADE, dummy, this);
+ success = true;
}
finally {
- dbh.close();
- }
-
- dbh = db.openForUpdate(dummy);
- p = new ProgramDB(dbh, OpenMode.UPGRADE, dummy, this);
-
- if (!p.isChanged()) {
- throw new RuntimeException(p + " uses an older version and was not upgraded.");
+ if (!success) {
+ dbh.close();
+ }
}
+ // FIXME: Need to track and release Program instance after DTM use is complete (GP-6895)
return p.getListing().getDataTypeManager();
}
diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiArguments.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiArguments.java
index 64eceb37ae2..964faf7ae78 100644
--- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiArguments.java
+++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiArguments.java
@@ -22,7 +22,7 @@
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.Connector.Argument;
-import ghidra.pty.ShellUtils;
+import ghidra.pty.ShellUtils.Shell;
public class JdiArguments {
enum Mode {
@@ -101,7 +101,7 @@ public void putArguments(Map args) {
}
String cp = env.get("OPT_TARGET_CLASSPATH");
if (!cp.isBlank()) {
- args.get("options").setValue("-cp " + ShellUtils.generateArgument(cp));
+ args.get("options").setValue("-cp " + Shell.LOCAL.generateArgument(cp));
}
}
}
diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java
index d002477d462..4cb6b0da88b 100644
--- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java
+++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java
@@ -50,6 +50,7 @@
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.pty.*;
+import ghidra.pty.ShellUtils.Shell;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
import ghidra.util.*;
@@ -475,7 +476,7 @@ protected PtyTerminalSession runInTerminal(List commandLine, Map withoutPath = ShellUtils.removePath(commandLine);
- terminal.setSubTitle(ShellUtils.generateLine(withoutPath));
+ terminal.setSubTitle(ShellUtils.generateLine(withoutPath, Shell.DISPLAY));
TerminalListener resizeListener = new TerminalListener() {
@Override
public void resized(short cols, short rows) {
@@ -490,8 +491,19 @@ public void resized(short cols, short rows) {
terminal.addTerminalListener(resizeListener);
env.put("TERM", "xterm-256color");
- PtySession session =
- pty.getChild().session(commandLine.toArray(String[]::new), env, workingDirectory);
+ PtySession session;
+ try {
+ session =
+ pty.getChild().session(commandLine.toArray(String[]::new), env, workingDirectory);
+ }
+ catch (Throwable t) {
+ terminal.terminated(-1);
+ pty.close();
+ for (TerminalSession ss : subordinates) {
+ ss.terminate();
+ }
+ throw t;
+ }
Thread waiter = new Thread(() -> {
try {
@@ -718,6 +730,7 @@ public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator config
* terminates early
*/
monitor.setMessage("Waiting for connection");
+ monitor.addCancelledListener(acceptor::cancel);
connection = acceptOrSessionEnds(acceptor, backEnd);
connection.registerTerminals(sessions.values());
monitor.increment();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewAsciiExporter.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewAsciiExporter.java
deleted file mode 100644
index f31c1ffe8a5..00000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewAsciiExporter.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* ###
- * 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.app.plugin.core.debug.export;
-
-import ghidra.app.util.exporter.AsciiExporter;
-import ghidra.framework.model.DomainObject;
-import ghidra.trace.model.Trace;
-
-public class TraceViewAsciiExporter extends AsciiExporter {
- @Override
- public boolean canExportDomainObject(Class extends DomainObject> domainObjectClass) {
- return Trace.class.isAssignableFrom(domainObjectClass);
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewBinaryExporter.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewBinaryExporter.java
deleted file mode 100644
index 2949322a91b..00000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewBinaryExporter.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* ###
- * 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.app.plugin.core.debug.export;
-
-import ghidra.app.util.exporter.BinaryExporter;
-import ghidra.framework.model.DomainObject;
-import ghidra.trace.model.Trace;
-
-public class TraceViewBinaryExporter extends BinaryExporter {
- @Override
- public boolean canExportDomainObject(Class extends DomainObject> domainObjectClass) {
- return Trace.class.isAssignableFrom(domainObjectClass);
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewHtmlExporter.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewHtmlExporter.java
deleted file mode 100644
index b9c581e2c98..00000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewHtmlExporter.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* ###
- * 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.app.plugin.core.debug.export;
-
-import ghidra.app.util.exporter.HtmlExporter;
-import ghidra.framework.model.DomainObject;
-import ghidra.trace.model.Trace;
-
-public class TraceViewHtmlExporter extends HtmlExporter {
- @Override
- public boolean canExportDomainObject(Class extends DomainObject> domainObjectClass) {
- return Trace.class.isAssignableFrom(domainObjectClass);
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewIntelHexExporter.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewIntelHexExporter.java
deleted file mode 100644
index 96c85d60fd5..00000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewIntelHexExporter.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* ###
- * 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.app.plugin.core.debug.export;
-
-import ghidra.app.util.exporter.IntelHexExporter;
-import ghidra.framework.model.DomainObject;
-import ghidra.trace.model.Trace;
-
-public class TraceViewIntelHexExporter extends IntelHexExporter {
- @Override
- public boolean canExportDomainObject(Class extends DomainObject> domainObjectClass) {
- return Trace.class.isAssignableFrom(domainObjectClass);
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewXmlExporter.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewXmlExporter.java
deleted file mode 100644
index 46ab514eb6d..00000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/export/TraceViewXmlExporter.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* ###
- * 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.app.plugin.core.debug.export;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-import ghidra.app.util.*;
-import ghidra.app.util.exporter.XmlExporter;
-import ghidra.framework.model.DomainObject;
-import ghidra.trace.model.Trace;
-
-// TODO: perhaps getApplicableExporters should use domainObject's class, not file's object class.
-// TODO: Where un-supported, be less abrasive, e.g., present empty managers.
-public class TraceViewXmlExporter extends XmlExporter {
- private final Map hideOpts = Map.of(
- "Properties", false,
- "Relocation Table", false,
- "External Libraries", false);
-
- @Override
- public boolean canExportDomainObject(Class extends DomainObject> domainObjectClass) {
- return Trace.class.isAssignableFrom(domainObjectClass);
- }
-
- @Override
- public List
+
gdt
+
+
+
File extension given to a compressed Ghidra Data Type Archive database file.
+
+
gzf
-
File extension given to Ghidra program database files that have been "zipped up".
+
File extension given to a compressed Ghidra Program database file.
+
+
gzt
+
+
+
File extension given to a compressed Ghidra Debugger Trace database file.
+
H
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/images/ImporterDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/images/ImporterDialog.png
index 3f70064d349..386d3f7c3b6 100644
Binary files a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/images/ImporterDialog.png and b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/images/ImporterDialog.png differ
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm
index 5a95a954945..cb701dc931e 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm
@@ -16,8 +16,8 @@
Introduction
-
Ghidra can import a variety of different types of files into a Ghidra project as Ghidra
- "programs." There are separate actions for importing single files, importing multiple
+
Ghidra can import a variety of different types of files into a Ghidra project.
+ There are separate actions for importing single files, importing multiple
files, and importing a file into an existing program. The actions for importing single or
multiple files into a new program are available in both the front-end project window or the
CodeBrowser tool. The action for adding to an existing program is only available from the
@@ -38,6 +38,7 @@
A results summary dialog will appear and, if successful, the new program will appear
+
A results summary dialog will appear and, if successful, the imported file will appear
in the project window and if initiated from a CodeBrowser tool, it will be opened in the
tool.
@@ -96,10 +97,12 @@
Alternative Steps (drag-and-drop):
Project Window: Drag a file from the system file explorer application and drop
onto the Ghidra Project Tree destination folder. Dropping onto the table view is not
- supported. In the case of Ghidra Zip File (GZF) or Ghidra Data Type Archive (GDT) file
- imports, an immediate unpack can be performed wthout a popup dialog if the Front End option
- Enable simple GZF/GDT unpack is enabled (
- EditTool Options...File ImportEnable simple GZF/GDT unpack).
+ supported. In the case of Ghidra Zip File (GZF), Ghidra Zip Debugger Trace file (GZT),
+ or Ghidra Data Type Archive (GDT) file
+ imports, an immediate unpack can be performed without a popup dialog if the Front End option
+ Enable simple GZF/GZT/GDT unpack is enabled (
+ EditTool Options...
+ File ImportEnable simple GZF/GZT/GDT unpack).
or Running Tool: Drag a file from the system file explorer application and drop
@@ -111,7 +114,7 @@
Alternative Steps (drag-and-drop):
Press OK to initiate the import.
-
A results summary dialog will appear and, if successful, the new program will appear
+
A results summary dialog will appear and, if successful, the newly imported file will appear
in the project window and if initiated from a CodeBrowser tool, it will be opened in the
tool.
@@ -263,20 +266,21 @@
Dialog Fields
file without any interpretation.
Language - This field specifies the language/compiler specification that will
- be used in the resulting program. Often, this will be automatically detected from the
+ be used when importing a program. Often, this will be automatically detected from the
file format. The Language/Compiler Spec Chooser
Dialog can be used to enter or change the language/compiler spec that will be
used.
Destination Folder - This field is used to specify the destination folder
- within the current project for where the newly imported program will be saved. If a
+ within the current project for where the newly imported file will be saved. If a
folder is selected in the front-end project window, then this field will default to that
folder, otherwise the root folder will be the default. The ... will bring up a
dialog for changing the destination folder.
-
Program Name - This field specifies the name for the newly imported program.
- By default, it will be the name of the imported file with any format specific extenstion
- removed (e.g., .xml, .gzf). Path information at the beginning of this field
+
Name - This field specifies the name for the newly imported file into
+ the active project.
+ By default, it will be the name of the imported file with any format specific extension
+ removed (e.g., .xml, .gzf, .gzt, .gdt). Path information at the beginning of this field
will be used to create a destination folder in the current project under the root folder
specified by the Destination Folder field.
+ * PEF was developed by Apple for use in its classic Mac OS operating system. BeOS on PowerPC
+ * systems also uses PEF.
+ *
+ * @see PEF Structure
+ */
public class PefLoader extends AbstractProgramWrapperLoader {
public final static String PEF_NAME = "Preferred Executable Format (PEF)";
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/SomLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/SomLoader.java
index 362d73c2eb2..93514833518 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/SomLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/SomLoader.java
@@ -50,18 +50,16 @@ public Collection findSupportedLoadSpecs(ByteProvider provider) throws
try {
SomHeader header = new SomHeader(new BinaryReader(provider, false));
- if (header.hasValidMagic() && header.hasValidVersionId()) {
- List results = QueryOpinionService.query(getName(),
- Integer.toString(header.getSystemId()), null);
- for (QueryResult result : results) {
- loadSpecs.add(new LoadSpec(this, 0, result));
- }
- if (loadSpecs.isEmpty()) {
- loadSpecs.add(new LoadSpec(this, 0, true));
- }
+ List results = QueryOpinionService.query(getName(),
+ Integer.toString(header.getSystemId()), null);
+ for (QueryResult result : results) {
+ loadSpecs.add(new LoadSpec(this, 0, result));
+ }
+ if (loadSpecs.isEmpty()) {
+ loadSpecs.add(new LoadSpec(this, 0, true));
}
}
- catch (IOException e) {
+ catch (SomException | IOException e) {
// that's ok, not a System Object Model
}
return loadSpecs;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/PersistentOpenCloseManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/PersistentOpenCloseManager.java
index 21c0af5e542..1be8c37d50b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/PersistentOpenCloseManager.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/PersistentOpenCloseManager.java
@@ -26,7 +26,6 @@
*/
public class PersistentOpenCloseManager implements OpenCloseManager {
private boolean openByDefault = true;
- private VoidPropertyMap booleanProperty;
private ProgramUserData programUserData;
// Often, isOpen will be called on the same function address many times in a row so cache
@@ -35,32 +34,53 @@ public class PersistentOpenCloseManager implements OpenCloseManager {
private boolean cachedResult;
private String defaultOpenClosePropertyname;
+ private String owner;
+ private String propertyName;
+ private VoidPropertyMap cachedPropertyMap;
+
public PersistentOpenCloseManager(ProgramUserData data, String owner, String propertyName) {
+ this.owner = owner;
+ this.propertyName = propertyName;
this.defaultOpenClosePropertyname = propertyName + "Default";
programUserData = data;
- int tx = programUserData.startTransaction();
- try {
- booleanProperty =
- programUserData.getBooleanProperty(owner, propertyName, true);
- }
- finally {
- programUserData.endTransaction(tx);
- }
-
// Get the default open state. Only addresses different from default have properties stored.
String functionState =
programUserData.getStringProperty(defaultOpenClosePropertyname, "Open");
openByDefault = functionState.equals("Open");
}
+ private VoidPropertyMap getExistingProperty() {
+ if (cachedPropertyMap != null) {
+ return cachedPropertyMap;
+ }
+
+ cachedPropertyMap = programUserData.getBooleanProperty(owner, propertyName, false);
+ return cachedPropertyMap;
+ }
+
+ private VoidPropertyMap getOrCreatePropertyMap() {
+ if (cachedPropertyMap != null) {
+ return cachedPropertyMap;
+ }
+
+ cachedPropertyMap = programUserData.getBooleanProperty(owner, propertyName, true);
+ return cachedPropertyMap;
+ }
+
@Override
public boolean isOpen(Address address) {
+ VoidPropertyMap propertyMap = getExistingProperty();
+ if (propertyMap == null) {
+ return openByDefault;
+ }
+
if (address.equals(cachedAddress)) {
return cachedResult;
}
+
cachedAddress = address;
- boolean contains = booleanProperty.hasProperty(address);
+ boolean contains = propertyMap.hasProperty(address);
cachedResult = openByDefault ? !contains : contains;
return cachedResult;
}
@@ -111,7 +131,8 @@ public void closeAll() {
private void addAddressProperty(Address address) {
int tx = programUserData.startTransaction();
try {
- booleanProperty.add(address);
+ VoidPropertyMap propertyMap = getOrCreatePropertyMap();
+ propertyMap.add(address);
}
finally {
programUserData.endTransaction(tx);
@@ -121,7 +142,8 @@ private void addAddressProperty(Address address) {
private void removeAddressProperty(Address address) {
int tx = programUserData.startTransaction();
try {
- booleanProperty.remove(address);
+ VoidPropertyMap propertyMap = getOrCreatePropertyMap();
+ propertyMap.remove(address);
}
finally {
programUserData.endTransaction(tx);
@@ -131,7 +153,8 @@ private void removeAddressProperty(Address address) {
private void clearProperties() {
int tx = programUserData.startTransaction();
try {
- booleanProperty.clear();
+ VoidPropertyMap propertyMap = getOrCreatePropertyMap();
+ propertyMap.clear();
}
finally {
programUserData.endTransaction(tx);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/xml/MemoryMapXmlMgr.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/xml/MemoryMapXmlMgr.java
index eb5ea454b87..dad67fc21df 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/xml/MemoryMapXmlMgr.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/xml/MemoryMapXmlMgr.java
@@ -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.
@@ -32,6 +32,7 @@
import ghidra.util.xml.*;
import ghidra.xml.XmlElement;
import ghidra.xml.XmlPullParser;
+import utilities.util.FileUtilities;
class MemoryMapXmlMgr {
@@ -190,7 +191,12 @@ else if (element.getName().equals("BYTE_MAPPED")) {
private void setData(byte[] bytes, int offset, String directory, String fileName,
int fileOffset, int length, MessageLog log) throws IOException {
- File f = new File(directory, fileName);
+ File dir = new File(directory);
+ File f = new File(dir, fileName);
+ if (!FileUtilities.isPathContainedWithin(dir, f)) {
+ throw new FileNotFoundException(
+ "FILE_NAME entry escapes target directory: " + fileName);
+ }
RandomAccessFile binfile = new RandomAccessFile(f, "r");
//binfile.seek(fileOffset);
try {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java
index 3410837d294..0b85d0c8634 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java
@@ -177,7 +177,7 @@ private Component buildMainPanel() {
panel.add(buildFolderNameField());
panel.add(buildFolderNameBrowseButton());
- panel.add(new GLabel("Program Name: ", SwingConstants.RIGHT));
+ panel.add(new GLabel("Name: ", SwingConstants.RIGHT));
panel.add(buildNameTextField());
panel.add(new JLabel());
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
index b57b12761ba..c08cff35db6 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
@@ -86,7 +86,7 @@ public class ImporterPlugin extends Plugin
"This plugin manages importing files, including those contained within " +
"firmware/filesystem images.";
- private static final String SIMPLE_UNPACK_OPTION = "Enable simple GZF/GDT unpack";
+ private static final String SIMPLE_UNPACK_OPTION = "Enable simple GZF/GZT/GDT unpack";
private static final boolean SIMPLE_UNPACK_OPTION_DEFAULT = false;
private DockingAction importAction;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java
index 196efc6fa56..03db3a8ea6b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java
@@ -450,32 +450,44 @@ private static Set doPostImportProcessing(PluginTool pluginTool,
ProgramManager programManager, LoadResults extends DomainObject> loadResults,
String importMessages, TaskMonitor monitor) throws CancelledException {
- boolean firstProgram = true;
+ boolean firstFile = true;
Set importedFilesSet = new HashSet<>();
for (Loaded extends DomainObject> loaded : loadResults) {
monitor.checkCancelled();
Object consumer = new Object();
DomainObject obj = loaded.getDomainObject(consumer);
+ DomainFile df = obj.getDomainFile();
try {
if (obj instanceof Program) {
if (programManager != null) {
- int openState = firstProgram
+ int openState = firstFile
? ProgramManager.OPEN_CURRENT
: ProgramManager.OPEN_VISIBLE;
programManager.openProgram((Program) obj, openState);
}
- importedFilesSet.add(obj.getDomainFile());
}
- if (firstProgram) {
+ else {
+ // We imported a non-Program (i.e., a Trace or similar).
+ // Try to open it in the current tool (if not FrontEndTool).
+ if (!(pluginTool instanceof FrontEndTool)) {
+ boolean success = pluginTool.acceptDomainFiles(new DomainFile[] { df });
+ if (!success) {
+ importMessages = "Saved " + df +
+ ", but failed to open it in the current tool.\n" + importMessages;
+ }
+ }
+ }
+ if (firstFile) {
// currently we only show results for the imported program, not any libraries
- displayResults(pluginTool, obj, obj.getDomainFile(), importMessages);
+ displayResults(pluginTool, obj, importMessages);
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && !importMessages.isEmpty()) {
Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages);
}
}
- firstProgram = false;
+ firstFile = false;
+ importedFilesSet.add(df);
}
finally {
obj.release(consumer);
@@ -506,7 +518,7 @@ public static void addContentToProgram(PluginTool tool, Program program, FSRL fs
program.getDomainFile().getPathname(), false, loadSpec, options, consumer,
messageLog, monitor);
loadSpec.getLoader().loadInto(program, settings);
- displayResults(tool, program, program.getDomainFile(), messageLog.toString());
+ displayResults(tool, program, messageLog.toString());
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && messageLog.hasMessages()) {
@@ -526,18 +538,9 @@ public static void addContentToProgram(PluginTool tool, Program program, FSRL fs
}
- private static void displayResults(PluginTool tool, DomainObject obj, DomainFile df,
- String info) {
-
- DomainFile domainFile = obj.getDomainFile();
- Map metadata = obj.getMetadata();
- if (df != null) {
- domainFile = df;
- metadata = df.getMetadata();
- }
-
+ private static void displayResults(PluginTool tool, DomainObject obj, String info) {
HelpLocation helpLocation = new HelpLocation(GenericHelpTopics.ABOUT, "About_Program");
- AboutDomainObjectUtils.displayInformation(tool, domainFile, metadata,
+ AboutDomainObjectUtils.displayInformation(tool, obj.getDomainFile(), obj.getMetadata(),
"Import Results Summary", info, helpLocation);
}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java
index 6a9290b0e7b..8c11d8a3b25 100644
--- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java
+++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java
@@ -123,11 +123,14 @@ public void testVerifyLoadedProgramDataTypes() throws Exception {
Iterator structures = dtm.getAllStructures();
Structure struct = structures.next(); // there is only 1 struct in the example XML dump
- assertEquals("Component count didn't match.", 20, struct.getNumComponents());
+ assertEquals("Component count didn't match.", 22, struct.getNumComponents());
assertEquals("Struct name is incorrect", "IMAGE_DOS_HEADER", struct.getName());
DataTypeComponent array = struct.getComponentAt(0); // the first component is an array
assertEquals("Array component name doesn't match", "e_magic", array.getFieldName());
assertEquals("Array wasn't sized right", 2, array.getLength());
+ DataTypeComponent bitField = struct.getComponentAt(65); // last component is a bitfield
+ assertEquals("Bitfield name doesn't match", "low_nibble", bitField.getFieldName());
+ assertEquals("Bitfield length doesn't match", "1", bitField.getLength() + "");
}
finally {
program.release(this);
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/model/symbol/SymbolUtilitiesNamingTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/model/symbol/SymbolUtilitiesNamingTest.java
new file mode 100644
index 00000000000..483586eff8c
--- /dev/null
+++ b/Ghidra/Features/Base/src/test/java/ghidra/program/model/symbol/SymbolUtilitiesNamingTest.java
@@ -0,0 +1,92 @@
+/* ###
+ * 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.symbol;
+
+import static ghidra.program.model.symbol.SymbolUtilities.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import ghidra.util.StringUtilities;
+
+public class SymbolUtilitiesNamingTest {
+
+ @Test
+ public void testGoodStringObjectPassthru() {
+ String s = "testsym";
+ assertSame(s, replaceInvalidChars(s, OMIT_BAD_CHARS));
+
+ s = "test sym";
+ assertNotSame(s, replaceInvalidChars(s, OMIT_BAD_CHARS));
+ }
+
+ @Test
+ public void testNullString() {
+ assertNull(replaceInvalidChars(null, OMIT_BAD_CHARS));
+ assertNull(SymbolUtilities.replaceInvalidChars(null, true));
+ }
+
+ @Test
+ public void testNullChar() {
+ assertEquals("testsym", replaceInvalidChars("test\0sym", OMIT_BAD_CHARS));
+ assertTrue(SymbolUtilities.isInvalidCodePoint(0));
+ }
+
+ @Test
+ public void testBOMChar() {
+ assertEquals("testsym",
+ replaceInvalidChars(
+ "test" + Character.toString(StringUtilities.UNICODE_BE_BYTE_ORDER_MARK) + "sym",
+ OMIT_BAD_CHARS));
+ }
+
+ @Test
+ public void testRTLOChar() {
+ assertEquals("testsym", replaceInvalidChars("test\u202esym", OMIT_BAD_CHARS));
+ }
+
+ @Test
+ public void testBadCharRemoval() {
+ assertEquals("testsym", replaceInvalidChars("test sym", OMIT_BAD_CHARS));
+ assertEquals("testsym", replaceInvalidChars("test\u007fsym", OMIT_BAD_CHARS));
+ assertEquals("testsym", replaceInvalidChars("test\tsym", OMIT_BAD_CHARS));
+
+ assertEquals("test\uaabbsym", replaceInvalidChars("test\uaabbsym", OMIT_BAD_CHARS));
+ }
+
+ @Test
+ public void testBadCharReplaceWithUnderscores() {
+ assertEquals("test_sym", replaceInvalidChars("test sym", USE_UNDERSCORES));
+ assertEquals("test_sym", replaceInvalidChars("test\u007fsym", USE_UNDERSCORES));
+ assertEquals("test_sym", replaceInvalidChars("test\tsym", USE_UNDERSCORES));
+ }
+
+ @Test
+ public void testBadCharReplaceWithCustom() {
+ assertEquals("test_4_sym",
+ replaceInvalidChars("test sym", (index, cp) -> "_" + index + "_"));
+ }
+
+ @Test
+ public void testAsciiRange() {
+ assertEquals(
+ "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
+ replaceInvalidChars(
+ "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
+ OMIT_BAD_CHARS));
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/test/resources/ghidra/app/util/opinion/decompile_debug_test.xml b/Ghidra/Features/Base/src/test/resources/ghidra/app/util/opinion/decompile_debug_test.xml
index ac6ce650362..61b0f0f8929 100644
--- a/Ghidra/Features/Base/src/test/resources/ghidra/app/util/opinion/decompile_debug_test.xml
+++ b/Ghidra/Features/Base/src/test/resources/ghidra/app/util/opinion/decompile_debug_test.xml
@@ -167,6 +167,9 @@ c4305fc3b907000000e8be03000090b9
+
+
+
diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest
index 54230f327fb..431c51275a2 100644
--- a/Ghidra/Features/Decompiler/certification.manifest
+++ b/Ghidra/Features/Decompiler/certification.manifest
@@ -94,6 +94,7 @@ src/decompile/datatests/twodim.xml||GHIDRA||||END|
src/decompile/datatests/union_datatype.xml||GHIDRA||||END|
src/decompile/datatests/varcross.xml||GHIDRA||||END|
src/decompile/datatests/wayoffarray.xml||GHIDRA||||END|
+src/decompile/datatests/wraprange.xml||GHIDRA||||END|
src/decompile/zlib/README.txt||GHIDRA||||END|
src/main/doc/commonprofile.xsl||GHIDRA||||END|
src/main/doc/cspec.xml||GHIDRA||||END|
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh
index 510432e5a2f..6fdc9a5478b 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh
@@ -87,6 +87,7 @@ public:
Address operator+(int8 off) const; ///< Increment address by a number of bytes
Address operator-(int8 off) const; ///< Decrement address by a number of bytes
friend ostream &operator<<(ostream &s,const Address &addr); ///< Write out an address to stream
+ bool isValidRange(uint8 size) const; ///< Is the range properly contained in its address space
bool containedBy(int4 sz,const Address &op2,int4 sz2) const; ///< Determine if \e op2 range contains \b this range
int4 justifiedContain(int4 sz,const Address &op2,int4 sz2,bool forceleft) const; ///< Determine if \e op2 is the least significant part of \e this.
int4 overlap(int4 skip,const Address &op,int4 size) const; ///< Determine how \b this address falls in a given address range
@@ -465,6 +466,14 @@ inline Address Address::operator-(int8 off) const {
return Address(base,base->wrapOffset(offset-off));
}
+/// If the range starting at \b this address and extending for \b size bytes, encompasses bytes beyond
+/// the edge of the address space (or wraps), then return \b false.
+/// \param size is the number of bytes in the range (must be non-zero)
+/// \return \b true if the range is properly contained in the address space
+inline bool Address::isValidRange(uint8 size) const {
+ return (size-1) <= (base->getHighest() - offset);
+}
+
/// This method is equivalent to Address::overlap, but a range in the \e join space can be
/// considered overlapped with its constituent pieces.
/// If \e this + \e skip falls in the range, \e op to \e op + \e size, then a non-negative integer is
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc
index 2232a823d50..4c2e8f2eb14 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc
@@ -104,8 +104,15 @@ Varnode *Funcdata::newUnique(int4 s,Datatype *ct)
Varnode *Funcdata::newVarnodeOut(int4 s,const Address &m,PcodeOp *op)
{
+ Varnode *vn;
Datatype *ct = glb->types->getBase(s,TYPE_UNKNOWN);
- Varnode *vn = vbank.createDef(s,m,ct,op);
+ if (m.isValidRange(s)) {
+ vn = vbank.createDef(s,m,ct,op);
+ }
+ else {
+ Address addr = glb->constructWrappingAddress(m, s);
+ vn = vbank.createDef(s,addr,ct,op);
+ }
op->setOutput(vn);
assignHigh(vn);
@@ -152,8 +159,13 @@ Varnode *Funcdata::newVarnode(int4 s,const Address &m,Datatype *ct)
if (ct == (const Datatype *)0)
ct = glb->types->getBase(s,TYPE_UNKNOWN);
-
- vn = vbank.create(s,m,ct);
+ if (m.isValidRange(s)) {
+ vn = vbank.create(s,m,ct);
+ }
+ else {
+ Address addr = glb->constructWrappingAddress(m, s);
+ vn = vbank.create(s,addr,ct);
+ }
assignHigh(vn);
if (s >= minLanedSize)
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/space.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/space.hh
index bc88fcd6c58..ff49ee951c9 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/space.hh
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/space.hh
@@ -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.
@@ -94,7 +94,8 @@ public:
truncated = 0x100, ///< Space is truncated from its original size, expect pointers larger than this size
hasphysical = 0x200, ///< Has physical memory associated with it
is_otherspace = 0x400, ///< Quick check for the OtherSpace derived class
- has_nearpointers = 0x800 ///< Does there exist near pointers into this space
+ has_nearpointers = 0x800, ///< Does there exist near pointers into this space
+ allows_wrapped_range = 0x1000 ///< A memory range for \b this space can wrap from high addresses to low
};
private:
spacetype type; ///< Type of space (PROCESSOR, CONSTANT, INTERNAL, ...)
@@ -150,6 +151,7 @@ public:
bool isOtherSpace(void) const; ///< Return \b true if \b this is the \e other address space
bool isTruncated(void) const; ///< Return \b true if this space is truncated from its original size
bool hasNearPointers(void) const; ///< Return \b true if \e near (truncated) pointers into \b this space are possible
+ bool allowsWrappedRange(void) const; ///< Return \b true if memory range can span high to low addresses in \b this space
void printOffset(ostream &s,uintb offset) const; ///< Write an address offset to a stream
virtual int4 numSpacebase(void) const; ///< Number of base registers associated with this space
@@ -467,6 +469,10 @@ inline bool AddrSpace::hasNearPointers(void) const {
return ((flags&has_nearpointers)!=0);
}
+inline bool AddrSpace::allowsWrappedRange(void) const {
+ return ((flags & allows_wrapped_range)!=0);
+}
+
/// Some spaces are "virtual", like the stack spaces, where addresses are really relative to a
/// base pointer stored in a register, like the stackpointer. This routine will return non-zero
/// if \b this space is virtual and there is 1 (or more) associated pointer registers
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/translate.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/translate.cc
index 8cca59a574a..d5b5219b14c 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/translate.cc
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/translate.cc
@@ -61,6 +61,7 @@ SpacebaseSpace::SpacebaseSpace(AddrSpaceManager *m,const Translate *t,const stri
contain = base;
hasbaseregister = false; // No base register assigned yet
isNegativeStack = true; // default stack growth
+ setFlags(allows_wrapped_range);
if (isFormal)
setFlags(formal_stackspace);
}
@@ -73,9 +74,10 @@ SpacebaseSpace::SpacebaseSpace(AddrSpaceManager *m,const Translate *t,const stri
SpacebaseSpace::SpacebaseSpace(AddrSpaceManager *m,const Translate *t)
: AddrSpace(m,t,IPTR_SPACEBASE)
{
+ contain = (AddrSpace *)0;
hasbaseregister = false;
isNegativeStack = true;
- setFlags(programspecific);
+ setFlags(programspecific | allows_wrapped_range);
}
/// This routine sets the base register associated with this \b virtual space
@@ -847,6 +849,38 @@ Address AddrSpaceManager::constructJoinAddress(const Translate *translate,
return join->getUnified().getAddr();
}
+/// Check if the address space allows wrapped ranges. If so, construct a \e joined address
+/// out of the high address piece of the range and the low address piece.
+/// \param addr is the initial address in the range
+/// \param size is the number of bytes in the range
+/// \return the address representing the wrapped range
+Address AddrSpaceManager::constructWrappingAddress(const Address &addr,int4 size)
+
+{
+ AddrSpace *spc = addr.getSpace();
+ if (!spc->isHeritaged())
+ return addr; // Size is ignored
+ uintb dist = spc->getHighest() - addr.getOffset() + 1;
+ if (size <= dist)
+ return addr;
+ if (!spc->allowsWrappedRange())
+ throw LowlevelError("Trying to construct memory range beyond end of address space: "+spc->getName());
+ int4 sizehi = (int4)dist;
+ int4 sizelo = size - sizehi;
+ vector pieces;
+ pieces.emplace_back();
+ pieces.emplace_back();
+ int4 highIndex = spc->isBigEndian() ? 0 : 1;
+ pieces[highIndex].space = spc;
+ pieces[highIndex].offset = addr.getOffset();
+ pieces[highIndex].size = sizehi;
+ pieces[1-highIndex].space = spc;
+ pieces[1-highIndex].offset = 0;
+ pieces[1-highIndex].size = sizelo;
+ JoinRecord *join = findAddJoin(pieces,0);
+ return join->getUnified().getAddr();
+}
+
/// If an Address in the \e join AddressSpace is shifted from its original offset, it may no
/// longer have a valid JoinRecord. The shift or size change may even make the address of
/// one of the pieces a more natural representation. Given a new Address and size, this method
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/translate.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/translate.hh
index 5abc6550f6f..0048fb772fb 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/translate.hh
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/translate.hh
@@ -278,6 +278,9 @@ public:
/// \brief Build a logical whole from register pairs
Address constructJoinAddress(const Translate *translate,const Address &hiaddr,int4 hisz,const Address &loaddr,int4 losz);
+ /// \brief Build a logical whole representing a range that \e wraps from a high address to a low address
+ Address constructWrappingAddress(const Address &addr,int4 size);
+
/// \brief Make sure a possibly offset \e join address has a proper JoinRecord
void renormalizeJoinAddress(Address &addr,int4 size);
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc
index e3601d3a99e..e8c7f511e2f 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc
@@ -120,6 +120,26 @@ void print_data(ostream &s,uint1 *buffer,int4 size,const Address &baseaddr)
}
}
+/// Construct a data-type providing just the size, alignment, and meta-type.
+/// Sets up the default configuration, which may be overridden by the derived constructor.
+/// \param s is the size in bytes
+/// \param align is the byte alignment required for \b this
+/// \param m is the meta-type
+Datatype::Datatype(int4 s,int4 align,type_metatype m)
+
+{
+ if (s < 0)
+ throw LowlevelError("Bad data-type size");
+ size = s;
+ metatype = m;
+ submeta = base2sub[m];
+ flags = 0;
+ id = 0;
+ typedefImm = (Datatype *)0;
+ alignment = align;
+ alignSize = s;
+}
+
/// If \b this and the other given data-type are both variable length and come from the
/// the same base data-type, return \b true.
/// \param ct is the other given data-type to compare with \b this
@@ -4052,7 +4072,7 @@ Datatype *TypeFactory::getBase(int4 s,type_metatype m)
{
Datatype *ct;
- if (s<9) {
+ if ((uint4)s<9) {
if (m >= TYPE_FLOAT) {
ct = typecache[s][m-TYPE_FLOAT];
if (ct != (Datatype *)0)
@@ -4061,13 +4081,9 @@ Datatype *TypeFactory::getBase(int4 s,type_metatype m)
}
else if (m==TYPE_FLOAT) {
if (s==10)
- ct = typecache10;
- else if (s==16)
- ct = typecache16;
- else
- ct = (Datatype *)0;
- if (ct != (Datatype *)0)
- return ct;
+ return typecache10;
+ if (s==16)
+ return typecache16;
}
if (s > glb->max_basetype_size) {
// Create array of unknown bytes to match size
@@ -4098,7 +4114,7 @@ Datatype *TypeFactory::getBase(int4 s,type_metatype m,const string &n)
Datatype *TypeFactory::getTypeChar(int4 s)
{
- if (s < 5) {
+ if ((uint4)s < 5) {
Datatype *res = charcache[s];
if (res != (Datatype *)0)
return res;
diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh
index a49726b0f4e..b1160c54baf 100644
--- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh
+++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.hh
@@ -210,12 +210,10 @@ protected:
protected:
static int4 calcAlignSize(int4 sz,int4 align); ///< Calculate aligned size, given size and alignment of data-type
public:
- /// Construct the base data-type copying low-level properties of another
+ /// \brief Construct the base data-type copying low-level properties of another
Datatype(const Datatype &op) { size = op.size; name=op.name; displayName=op.displayName; metatype=op.metatype;
submeta=op.submeta; flags=op.flags; id=op.id; typedefImm=op.typedefImm; alignment=op.alignment; alignSize=op.alignSize; }
- /// Construct the base data-type providing size and meta-type
- Datatype(int4 s,int4 align,type_metatype m) {
- size=s; metatype=m; submeta=base2sub[m]; flags=0; id=0; typedefImm=(Datatype *)0; alignment=align; alignSize=s; }
+ Datatype(int4 s,int4 align,type_metatype m); ///< Constructor
virtual ~Datatype(void) {} ///< Destructor
bool isCoreType(void) const { return ((flags&coretype)!=0); } ///< Is this a core data-type
bool isCharPrint(void) const { return ((flags&(chartype|utf16|utf32|opaque_string))!=0); } ///< Does this print as a 'char'
diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/wraprange.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/wraprange.xml
new file mode 100644
index 00000000000..e9319228f0b
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/decompile/datatests/wraprange.xml
@@ -0,0 +1,19 @@
+
+
+
+
+4883ec04488b04244883c404c3
+
+
+
+
+j\{0x00000000,0xfffffffffffffffc\}.* = CONCAT44\(s0x00000000:4\(i\),s0xfffffffffffffffc:4\(i\)\)
+RAX\(.*\) = j\{0x00000000,0xfffffffffffffffc\}
+
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/analysis/DecompilerSwitchAnalyzer.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/analysis/DecompilerSwitchAnalyzer.java
index af699b90abf..d632172e4c8 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/analysis/DecompilerSwitchAnalyzer.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/analysis/DecompilerSwitchAnalyzer.java
@@ -478,7 +478,8 @@ private boolean resolveComputableFlow(Address location, TaskMonitor monitor,
// then it isn't a switch statment
//
// NOTE: Assumption, we have found all flows leading to the switch that might split the basic block
-
+ // NOTE: This assumption is a problem for internal pcode branching. Need to check for that.
+
final AtomicInteger foundCount = new AtomicInteger(0);
SymbolicPropogator prop = new SymbolicPropogator(program,false);
prop.flowConstants(jumpBlockAt.getFirstStartAddress(), jumpBlockAt,
@@ -493,6 +494,27 @@ public boolean evaluateReference(VarnodeContext context, Instruction instr,
}
return false;
}
+
+ @Override
+ public boolean evaluateContextBefore(VarnodeContext context, Instruction instr) {
+ // There shouldn't be any branching because a branch would split a block
+ // but there could be internal branches, as the basic block algorithm can't
+ // detect these cases correctly.
+ PcodeOp[] pcode = instr.getPcode();
+ for (PcodeOp pcodeOp : pcode) {
+ if (pcodeOp.getOpcode() == PcodeOp.CBRANCH) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean followFalseConditionalBranches() {
+ // There shouldn't be any because a branch would split a block
+ // but there could be internal branches.
+ return false;
+ }
}, false, monitor);
// only found one reference
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToHighSymbolAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToHighSymbolAction.java
index 866fc659bb3..3e50bcd77d3 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToHighSymbolAction.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToHighSymbolAction.java
@@ -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.
@@ -21,7 +21,9 @@
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.app.util.HelpTopics;
-import ghidra.program.model.listing.Function;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.LabelFieldLocation;
@@ -61,16 +63,27 @@ protected boolean isEnabledForDecompilerContext(DecompilerActionContext context)
updateMenuName(function.getName());
return true;
}
+
ClangToken token = context.getTokenAtCursor();
if (token == null) {
return false;
}
+
HighSymbol highSymbol = token.getHighSymbol(context.getHighFunction());
+ if (highSymbol == null) {
+ return false;
+ }
- if (highSymbol == null || highSymbol.getStorage().isBadStorage() ||
- !highSymbol.isGlobal()) {
+ if (highSymbol.getStorage().isBadStorage() || !highSymbol.isGlobal()) {
return false;
}
+
+ VariableStorage storage = highSymbol.getStorage();
+ Address addr = storage.getMinAddress();
+ if (addr == null) {
+ return false;
+ }
+
updateMenuName(highSymbol.getName());
return true;
}
@@ -85,17 +98,23 @@ protected void decompilerActionPerformed(DecompilerActionContext context) {
"Please add the plugin implementing this service.");
return;
}
+
LabelFieldLocation location = null;
Function function = getFunction(context);
if (function != null && !(function instanceof UndefinedFunction)) {
location = new LabelFieldLocation(function.getSymbol());
}
else {
- HighSymbol highSymbol =
- context.getTokenAtCursor().getHighSymbol(context.getHighFunction());
- location = new LabelFieldLocation(context.getProgram(),
- highSymbol.getStorage().getMinAddress(), highSymbol.getName());
+ ClangToken token = context.getTokenAtCursor();
+ HighFunction highFunction = context.getHighFunction();
+ HighSymbol highSymbol = token.getHighSymbol(highFunction);
+ Program program = context.getProgram();
+ VariableStorage storage = highSymbol.getStorage();
+ Address minAddress = storage.getMinAddress();
+ String symbolName = highSymbol.getName();
+ location = new LabelFieldLocation(program, minAddress, symbolName);
}
+
DecompilerProvider provider = context.getComponentProvider();
service.showReferencesToLocation(location, provider);
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/exporter/CppExporter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/exporter/CppExporter.java
index 529fceb7a4b..7d04575e3b0 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/exporter/CppExporter.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/util/exporter/CppExporter.java
@@ -41,7 +41,7 @@
import ghidra.util.task.*;
import util.CollectionUtils;
-public class CppExporter extends Exporter {
+public class CppExporter extends ProgramExporter {
public static final String CREATE_C_FILE = "Create C File (.c)";
public static final String CREATE_HEADER_FILE = "Create Header File (.h)";
@@ -90,13 +90,16 @@ public CppExporter(DecompileOptions options, boolean createHeader, boolean creat
@Override
public boolean export(File file, DomainObject domainObj, AddressSetView addrSet,
TaskMonitor monitor) throws IOException, ExporterException {
- if (!(domainObj instanceof Program)) {
+
+ Program program;
+ try {
+ program = getProgram(domainObj);
+ }
+ catch (ClassCastException e) {
log.appendMsg("Unsupported type: " + domainObj.getClass().getName());
return false;
}
- Program program = (Program) domainObj;
-
configureOptions(program);
configureFunctionTags(program);
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java
index 900f4106b61..0b7f43f6020 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerClangTest.java
@@ -26,8 +26,7 @@
import javax.swing.*;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.*;
import docking.action.DockingActionIf;
import docking.options.OptionsService;
@@ -44,7 +43,6 @@
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.decompile.actions.*;
import ghidra.app.util.AddEditDialog;
-import ghidra.framework.Application;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.listing.CommentType;
import ghidra.program.model.listing.Function;
@@ -958,6 +956,7 @@ public void testSecondaryHighlighting_MiddleMouseDoesNotClearSecondaryHighlight_
}
@Test
+ @Ignore("Resolved for 12.2")
public void testSecondaryHighlighting_CloneDecompiler() {
/*
@@ -979,12 +978,6 @@ public void testSecondaryHighlighting_CloneDecompiler() {
*/
- // TODO temp workaround; delete when 12.2 is released
- String version = Application.getApplicationVersion();
- if ("12.1".equals(version)) {
- return;
- }
-
decompile("100000d60"); // '_call_structure_A'
// 5:2 "_printf"
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java
index 19249412a9d..b0b34315b0c 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/eclipse/AndroidProjectCreator.java
@@ -115,8 +115,7 @@ private void createEclipseProjectDirectories() throws IOException {
}
private void processListing(File outputDirectory, GFileSystem fs, List listing,
- TaskMonitor monitor)
- throws IOException, CancelledException {
+ TaskMonitor monitor) throws IOException, CancelledException {
for (GFile child : listing) {
String childName = child.getName();
@@ -131,6 +130,10 @@ private void processListing(File outputDirectory, GFileSystem fs, List li
continue;
}
File subDir = new File(outputDirectory, childName);
+ if (!FileUtilities.isPathContainedWithin(outputDirectory, subDir)) {
+ Msg.error(this, "Skipping directory with path traversal: " + childName);
+ continue;
+ }
FileUtilities.checkedMkdir(subDir);
processListing(subDir, fs, child.getListing(), monitor);
continue;
@@ -220,6 +223,9 @@ private static File copyFile(ResourceFile inputFile, File outputDirectory, Strin
try (InputStream is = inputFile.getInputStream()) {
FileUtilities.checkedMkdirs(outputDirectory);
File destFile = new File(outputDirectory, outputName);
+ if (!FileUtilities.isPathContainedWithin(outputDirectory, destFile)) {
+ throw new IOException("Path traversal detected in entry name: " + outputName);
+ }
monitor.setMessage("Copying [" + inputFile.getName() + "] to Eclipse project...");
FileUtilities.copyStreamToFile(is, destFile, false, monitor);
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dmg/DmgServerProcessManager.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dmg/DmgServerProcessManager.java
index 23a2513fccb..f641625a0bd 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dmg/DmgServerProcessManager.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dmg/DmgServerProcessManager.java
@@ -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.
@@ -240,7 +240,7 @@ private Process createProcess() {
String java =
System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
- // optional: -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=18200
+ // optional: -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=18200
try {
Process p = Runtime.getRuntime().exec(
new String[] { java, "-classpath", classPath, "-Xmx" + dmgServerMemoryMB + "m", //need more memory to load and xfer data across pipe
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandProcessor.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandProcessor.java
index 93c9f414280..edff61a2f8d 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandProcessor.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandProcessor.java
@@ -224,12 +224,12 @@ static int parsePermission(String permissionStr) {
return -1;
}
- static File getCommandDir(File serverRootDir) {
- return new File(serverRootDir, ADMIN_CMD_DIR);
- }
-
- static File getOrCreateCommandDir(RepositoryManager repositoryMgr) {
- File cmdDir = getCommandDir(repositoryMgr.getRootDir());
+ static File getOrCreateCommandDir(File serverRootDir) {
+ if (!serverRootDir.isDirectory() || !serverRootDir.canWrite()) {
+ System.err.println("Insufficient privilege or server not started!");
+ System.exit(-1);
+ }
+ File cmdDir = new File(serverRootDir, ADMIN_CMD_DIR);
if (!cmdDir.exists()) {
// ensure process owner creates queued command directory
cmdDir.mkdir();
@@ -243,7 +243,7 @@ static File getOrCreateCommandDir(RepositoryManager repositoryMgr) {
* @throws IOException
*/
static void processCommands(RepositoryManager repositoryMgr) throws IOException {
- File cmdDir = getOrCreateCommandDir(repositoryMgr);
+ File cmdDir = getOrCreateCommandDir(repositoryMgr.getRootDir());
File[] files = cmdDir.listFiles(CMD_FILE_FILTER);
if (files == null) {
log.error("Failed to access command queue " + cmdDir.getAbsolutePath() +
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandWatcher.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandWatcher.java
index e3b4ffbe1cf..fd235b23ba4 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandWatcher.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/CommandWatcher.java
@@ -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.
@@ -37,7 +37,7 @@ public class CommandWatcher implements Runnable {
this.repositoryMgr = repositoryMgr;
watcher = FileSystems.getDefault().newWatchService();
- cmdDirPath = CommandProcessor.getOrCreateCommandDir(repositoryMgr).toPath();
+ cmdDirPath = CommandProcessor.getOrCreateCommandDir(repositoryMgr.getRootDir()).toPath();
cmdDirPath.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
}
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/Repository.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/Repository.java
index 95b0602692c..6a77e75acaf 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/Repository.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/Repository.java
@@ -17,6 +17,7 @@
import java.io.*;
import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -351,7 +352,7 @@ public void setUserList(String currentUser, User[] users, boolean allowAnonymous
validate();
validateAdminPrivilege(currentUser);
- Set allUsers = Set.of(mgr.getAllUsers(currentUser));
+ Set allUsers = getAllServerUsers();
Set updatedUsers = new HashSet<>();
LinkedHashMap newUserMap = new LinkedHashMap<>();
@@ -574,17 +575,20 @@ void delete(String currentUser) throws IOException, UserAccessException {
* Generate formatted list of user access permissions to the specified repository.
* This is intended to be used with the svrAdmin console command
* @param repositoryDir repository directory
+ * @param allUsers set of all valid users known to server
* @param pad padding string to be prefixed to each output line
* @return formatted list of user access permissions
*/
- static String getFormattedUserPermissions(File repositoryDir, String pad) {
+ static String getFormattedUserPermissions(File repositoryDir, Set allUsers,
+ String pad) {
StringBuilder buf = new StringBuilder();
File userAccessFile = new File(repositoryDir, ACCESS_CONTROL_FILENAME);
try {
List list = new ArrayList<>();
- boolean anonymousAccessAllowed = readAccessFile(userAccessFile, list);
+ AtomicBoolean anonymousAccessAllowed = new AtomicBoolean();
+ readAccessFile(userAccessFile, allUsers, anonymousAccessAllowed, list);
Collections.sort(list);
- if (anonymousAccessAllowed) {
+ if (anonymousAccessAllowed.get()) {
buf.append(pad + "* Anonymous read-only access permitted *\n");
}
for (User user : list) {
@@ -592,7 +596,7 @@ static String getFormattedUserPermissions(File repositoryDir, String pad) {
}
}
catch (IOException e) {
- System.out.println(pad + "Failed to read repository access file: " + e.getMessage());
+ System.err.println(pad + "Failed to read repository access file: " + e.getMessage());
}
return buf.toString();
}
@@ -602,17 +606,19 @@ static String getFormattedUserPermissions(File repositoryDir, String pad) {
* restricted to user names contained within listUserAccess.
* This is intended to be used with the svrAdmin console command
* @param repositoryDir repository directory
+ * @param allUsers set of all valid users known to server
* @param pad padding string to be prefixed to each output line
* @param listUserAccess set of user names of interest
* @return formatted list of user access permissions or null if no users of interest found
*/
- static String getFormattedUserPermissions(File repositoryDir, String pad,
+ static String getFormattedUserPermissions(File repositoryDir, Set allUsers, String pad,
Set listUserAccess) {
StringBuilder buf = null;
File userAccessFile = new File(repositoryDir, ACCESS_CONTROL_FILENAME);
try {
+ AtomicBoolean anonymousAccessAllowed = new AtomicBoolean();
ArrayList list = new ArrayList<>();
- readAccessFile(userAccessFile, list);
+ readAccessFile(userAccessFile, allUsers, anonymousAccessAllowed, list);
Collections.sort(list);
for (User user : list) {
if (!listUserAccess.contains(user.getName())) {
@@ -625,11 +631,15 @@ static String getFormattedUserPermissions(File repositoryDir, String pad,
}
}
catch (IOException e) {
- System.out.println(pad + "Failed to read repository access file: " + e.getMessage());
+ System.err.println(pad + "Failed to read repository access file: " + e.getMessage());
}
return buf != null ? buf.toString() : null;
}
+ private Set getAllServerUsers() {
+ return Set.of(mgr.getUserManager().getUsers());
+ }
+
/**
* Read user access list from local file.
* @throws IOException if an IO error occurs
@@ -639,9 +649,12 @@ private void readAccessFile() throws IOException {
return;
}
+ AtomicBoolean anonymousAccess = new AtomicBoolean();
ArrayList list = new ArrayList<>();
- anonymousAccessAllowed =
- readAccessFile(userAccessFile, list) && mgr.anonymousAccessAllowed();
+ boolean modified =
+ readAccessFile(userAccessFile, getAllServerUsers(), anonymousAccess, list) &&
+ mgr.anonymousAccessAllowed();
+ anonymousAccessAllowed = mgr.anonymousAccessAllowed() & anonymousAccess.get();
LinkedHashMap newUserMap = new LinkedHashMap<>();
boolean hasAdmin = false;
@@ -654,19 +667,27 @@ private void readAccessFile() throws IOException {
.info("WARNING: Repository '" + name + "' does not have an assigned Admin");
}
userMap = newUserMap;
+
+ if (modified) {
+ writeUserList(userMap, anonymousAccessAllowed);
+ }
}
/**
* Read list of user permissions from userAccessFile and determine if anonymous read-only
* access is permitted
* @param userAccessFile repository user access file
+ * @param allUsers set of all valid users known to server
+ * @param allowAnonymous which conveys the anonymous permission state read from the file
* @param users list to be populated with user permissions defined by userAccessFile
- * @return true if anonymous read-only access is permitted, else false
+ * @return true if one of more users were removed from the stored list
* @throws IOException if an IO error occurs
*/
- private static boolean readAccessFile(File userAccessFile, List users)
+ private static boolean readAccessFile(File userAccessFile, Set allUsers,
+ AtomicBoolean allowAnonymous, List users)
throws IOException {
- boolean allowAnonymous = false;
+ boolean userRemoved = false;
+ allowAnonymous.set(false);
try (BufferedReader reader = new BufferedReader(new FileReader(userAccessFile))) {
String line = "";
while (true) {
@@ -679,16 +700,21 @@ private static boolean readAccessFile(File userAccessFile, List users)
}
line = line.trim();
if (ANONYMOUS_STR.equals(line)) {
- allowAnonymous = true;
+ allowAnonymous.set(true);
continue;
}
User user = processAccessLine(line);
if (user != null) {
- users.add(user);
+ if (allUsers.contains(user.getName())) {
+ users.add(user);
+ }
+ else {
+ userRemoved = true;
+ }
}
}
}
- return allowAnonymous;
+ return userRemoved;
}
/**
@@ -986,7 +1012,7 @@ static boolean markRepositoryForIndexMigration(File serverDir, String repository
int indexVersion = IndexedLocalFileSystem.readIndexVersion(rootPath);
if (indexVersion >= IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
if (!silent) {
- System.err
+ System.out
.println("Repository '" + repositoryName + "' is already indexed!");
}
return false;
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java
index e0faae56931..d5fd7ed411f 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java
@@ -459,8 +459,10 @@ static void log(String repositoryName, String path, String msg) {
* @param repositoriesRootDir repositories root directory
* @param includeUserAccessDetails if true additional user access details will displayed
* for each repository
+ * @param allUsers set of all valid users known to server
*/
- static void listRepositories(File repositoriesRootDir, boolean includeUserAccessDetails) {
+ static void listRepositories(File repositoriesRootDir, boolean includeUserAccessDetails,
+ Set allUsers) {
String[] names = RepositoryManager.getRepositoryNames(repositoriesRootDir);
System.out.println("\nRepositories:");
if (names.length == 0) {
@@ -495,7 +497,7 @@ static void listRepositories(File repositoriesRootDir, boolean includeUserAccess
System.out.println(" " + name + (type == null ? "" : (" - uses " + type)));
if (includeUserAccessDetails) {
- System.out.print(Repository.getFormattedUserPermissions(repoDir, " "));
+ System.out.print(Repository.getFormattedUserPermissions(repoDir, allUsers, " "));
}
}
}
@@ -505,8 +507,10 @@ static void listRepositories(File repositoriesRootDir, boolean includeUserAccess
* This is intended to be used with the svrAdmin console command
* @param repositoriesRootDir repositories root directory
* @param usernameSet set of users whose details should be displayed
+ * @param allUsers set of all valid users known to server
*/
- static void listRepositories(File repositoriesRootDir, Set usernameSet) {
+ static void listRepositories(File repositoriesRootDir, Set usernameSet,
+ Set allUsers) {
String[] names = RepositoryManager.getRepositoryNames(repositoriesRootDir);
if (names.length == 0) {
System.out.println(" ");
@@ -518,7 +522,7 @@ static void listRepositories(File repositoriesRootDir, Set usernameSet)
File repoDir = new File(repositoriesRootDir, NamingUtilities.mangle(name));
String formattedAccessList =
- Repository.getFormattedUserPermissions(repoDir, " ", usernameSet);
+ Repository.getFormattedUserPermissions(repoDir, allUsers, " ", usernameSet);
if (formattedAccessList != null) {
if (outputHeader) {
System.out.println("\nRepositories:");
@@ -562,8 +566,8 @@ static void markAllRepositoriesForIndexMigration(File serverDir) {
* @throws IOException if error occured while updating repository access lists.
*/
void userRemoved(String username) throws IOException {
- for (String repName : getRepositoryNames()) {
- getRepository(repName).removeUser(username);
+ for (Repository repo : repositoryMap.values()) {
+ repo.removeUser(username);
}
}
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java
index 2526b304672..fe0a217f84d 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java
@@ -26,7 +26,8 @@
import ghidra.GhidraLaunchable;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
-import ghidra.util.*;
+import ghidra.util.Msg;
+import ghidra.util.NamingUtilities;
public class ServerAdmin implements GhidraLaunchable {
@@ -110,11 +111,7 @@ public void execute(String[] args) {
System.exit(-1);
}
- File cmdDir = CommandProcessor.getCommandDir(serverRootDir);
- if (!cmdDir.isDirectory() || !cmdDir.canWrite()) {
- System.err.println("Insufficient privilege or server not started!");
- System.exit(-1);
- }
+ File cmdDir = CommandProcessor.getOrCreateCommandDir(serverRootDir);
// Process command line
boolean listRepositories = false;
@@ -238,11 +235,18 @@ public void execute(String[] args) {
UserManager.listUsers(serverRootDir);
}
if (listRepositories) {
- if (listUsernameSet.isEmpty()) {
- RepositoryManager.listRepositories(serverRootDir, listAllUserPermissions);
+ try {
+ Set users = UserManager.getUsers(serverRootDir);
+ if (listUsernameSet.isEmpty()) {
+ RepositoryManager.listRepositories(serverRootDir, listAllUserPermissions,
+ users);
+ }
+ else {
+ RepositoryManager.listRepositories(serverRootDir, listUsernameSet, users);
+ }
}
- else {
- RepositoryManager.listRepositories(serverRootDir, listUsernameSet);
+ catch (IOException e) {
+ System.err.println("\nFailed to read user file: " + e.getMessage());
}
}
System.out.println();
@@ -477,7 +481,7 @@ private File getServerDirFromConfig(String configFilePath) {
File configFile = new File(configFilePath);
if (!configFile.exists()) {
- System.out.println("Config file not found: " + configFile.getAbsolutePath());
+ System.err.println("Config file not found: " + configFile.getAbsolutePath());
return null;
}
@@ -495,7 +499,7 @@ private File getServerDirFromConfig(String configFilePath) {
config.load(in);
}
catch (IOException e) {
- System.out.println("Failed to read " + configFile.getName() + ": " + e.getMessage());
+ System.err.println("Failed to read " + configFile.getName() + ": " + e.getMessage());
}
finally {
if (in != null) {
@@ -510,7 +514,7 @@ private File getServerDirFromConfig(String configFilePath) {
String p = config.getProperty(SERVER_DIR_CONFIG_PROPERTY);
if (p == null) {
- System.out.println("Failed to find property: " + SERVER_DIR_CONFIG_PROPERTY);
+ System.err.println("Failed to find property: " + SERVER_DIR_CONFIG_PROPERTY);
return null;
}
File dir = new File(p);
@@ -518,7 +522,7 @@ private File getServerDirFromConfig(String configFilePath) {
// Make relative repositories dir relative to installation root
ResourceFile installRoot = Application.getInstallationDirectory();
if (installRoot == null || installRoot.getFile(false) == null) {
- System.out.println("Failed to resolve installation root directory!");
+ System.err.println("Failed to resolve installation root directory!");
return null;
}
dir = new File(installRoot.getFile(false), p);
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java
index a64090f1f69..a57d8ad983d 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java
@@ -554,10 +554,26 @@ static void listUsers(File repositoriesRootDir) {
}
}
catch (IOException e) {
- System.out.println("\nFailed to read user file: " + e.getMessage());
+ System.err.println("\nFailed to read user file: " + e.getMessage());
}
}
+ /**
+ * Get list of all users known to server.
+ * @param repositoriesRootDir repositories root directory
+ * @return list of known users
+ * @throws IOException if error occurs while reading passwd file
+ */
+ static Set getUsers(File repositoriesRootDir) throws IOException {
+ File userFile = new File(repositoriesRootDir, USER_PASSWORD_FILE);
+
+ LinkedHashMap list = new LinkedHashMap<>();
+ HashMap lookupMap = new HashMap<>();
+
+ readUserList(userFile, list, lookupMap);
+ return list.keySet();
+ }
+
private static void readUserList(File file, Map usersIndexByName,
Map x500LookupMap) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java
index dfe58ae91da..5af537e8888 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java
@@ -25,8 +25,10 @@
import java.rmi.registry.Registry;
import java.rmi.server.*;
import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
import java.util.*;
+import javax.net.ssl.X509ExtendedKeyManager;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
import javax.security.auth.Subject;
@@ -36,16 +38,17 @@
import javax.security.auth.login.LoginException;
import javax.security.auth.x500.X500Principal;
+import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.bouncycastle.asn1.x509.GeneralName;
import generic.jar.ResourceFile;
import generic.random.SecureRandomFactory;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.framework.remote.*;
-import ghidra.net.DefaultKeyManagerFactory;
-import ghidra.net.DefaultSSLContextInitializer;
+import ghidra.net.*;
import ghidra.server.RepositoryManager;
import ghidra.server.UserManager;
import ghidra.server.security.*;
@@ -473,7 +476,7 @@ private static InetAddress findHost() {
private static String initRemoteAccessHostname() throws UnknownHostException {
String hostname = System.getProperty(RMI_SERVER_PROPERTY);
- if (hostname == null) {
+ if (StringUtils.isBlank(hostname)) {
if (bindAddress != null) {
hostname = bindAddress.getHostAddress();
}
@@ -482,7 +485,7 @@ private static String initRemoteAccessHostname() throws UnknownHostException {
if (localhost.isLoopbackAddress()) {
localhost = findHost();
if (localhost == null) {
- log.fatal("Can't find host ip address!");
+ log.fatal("Failed to identify host interface IP address");
System.exit(-1);
}
}
@@ -719,7 +722,7 @@ else if (s.equals("-autoProvision")) {
}
}
- if (rootPath == null) {
+ if (StringUtils.isBlank(rootPath)) {
displayUsage("Repository directory must be specified!");
System.exit(-1);
}
@@ -802,49 +805,28 @@ else if (s.equals("-autoProvision")) {
}
String preferredKeyStore = DefaultKeyManagerFactory.getPreferredKeyStore();
- if (preferredKeyStore == null) {
-
+ if (StringUtils.isBlank(preferredKeyStore)) {
// keystore has not been identified - use self-signed certificate
log.info(" Generating self-signed certificate...");
- log.info(" Subject Alternative Names:");
- log.info(" " + hostname);
-
- DefaultKeyManagerFactory.setDefaultIdentity(new X500Principal("CN=GhidraServer"));
- DefaultKeyManagerFactory.addSubjectAlternativeName(hostname);
-
- // Collect alternate hostnames for inclusion in certificate
- Enumeration nets = NetworkInterface.getNetworkInterfaces();
- while (nets.hasMoreElements()) {
- NetworkInterface netint = nets.nextElement();
- Enumeration addrs = netint.getInetAddresses();
- while (addrs.hasMoreElements()) {
- InetAddress addr = addrs.nextElement();
- altNames.add(addr.getHostAddress());
- altNames.add(addr.getHostName());
- altNames.add(addr.getCanonicalHostName());
- }
- }
- altNames.remove(hostname);
- for (String name : altNames) {
- log.info(" " + name);
- DefaultKeyManagerFactory.addSubjectAlternativeName(name);
- }
+ initSelfSignedCertificateData(hostname, altNames);
}
else {
log.info(" Using server certificate keystore: " + preferredKeyStore);
+ if (!altNames.isEmpty()) {
+ log.warn(" -ipAlt use ignored with installed server certificate");
+ }
}
if (!DefaultKeyManagerFactory.initialize()) {
log.fatal("Failed to initialize PKI/SSL keystore");
System.exit(0);
- return;
}
-
- // RMIClassServer.startServer(classSvrPort);
-
- // String codeBaseProp = "http://" +
- // localhost.getCanonicalHostName() + ":" + classSvrPort + "/";
- // System.setProperty(RMI_CODEBASE_PROPERTY, codeBaseProp);
+
+ int signingKeyCount = logServerCertificates("RSA") + logServerCertificates("ECDSA");
+ if (signingKeyCount == 0) {
+ log.fatal("Failed to locate a certificate with digital-signature usage");
+ System.exit(0);
+ }
log.info(" RMI Registry port: " + ServerPortFactory.getRMIRegistryPort());
log.info(" RMI SSL port: " + ServerPortFactory.getRMISSLPort());
@@ -852,8 +834,7 @@ else if (s.equals("-autoProvision")) {
log.info(" Block Stream compression: " +
(RemoteBlockStreamHandle.enableCompressedSerializationOutput ? "enabled"
: "disabled"));
-// log.info(" Class server port: " + ??);
- log.info(" Root: " + rootPath);
+ log.info(" Root: " + serverRoot.getAbsolutePath());
log.info(" Auth: " + authMode.getDescription());
if (authMode == PASSWORD_FILE_LOGIN && defaultPasswordExpiration >= 0) {
log.info(" Default password expiration: " +
@@ -920,6 +901,105 @@ public ServerSocket createServerSocket(int port) throws IOException {
System.exit(-1);
}
}
+
+ private static void initSelfSignedCertificateData(String preferredHostname,
+ Set altNames) throws SocketException {
+
+ DefaultKeyManagerFactory.setDefaultIdentity(new X500Principal("CN=GhidraServer"));
+ DefaultKeyManagerFactory.addSubjectAlternativeName(preferredHostname);
+
+ // Collect alternate hostnames for inclusion in certificate
+ Enumeration nets = NetworkInterface.getNetworkInterfaces();
+ while (nets.hasMoreElements()) {
+ NetworkInterface netint = nets.nextElement();
+ Enumeration addrs = netint.getInetAddresses();
+ while (addrs.hasMoreElements()) {
+ InetAddress addr = addrs.nextElement();
+ altNames.add(addr.getHostAddress());
+ altNames.add(addr.getHostName());
+ altNames.add(addr.getCanonicalHostName());
+ }
+ }
+ altNames.remove(preferredHostname); // already added as first entry
+ for (String name : altNames) {
+ DefaultKeyManagerFactory.addSubjectAlternativeName(name);
+ }
+ }
+
+ /**
+ * Log server certiifcates
+ * @param keyType
+ * @return number of certificates that support signing
+ */
+ private static int logServerCertificates(String keyType) {
+
+ X509ExtendedKeyManager km = DefaultKeyManagerFactory.getKeyManager();
+ String[] aliases = km.getServerAliases(keyType, null);
+ if (aliases == null) {
+ return 0;
+ }
+
+ String pad = " ";
+ Date now = new Date();
+ int signingCount = 0;
+
+ for (String alias : aliases) {
+
+ X509Certificate[] certificateChain = km.getCertificateChain(alias);
+ X509Certificate x509Cert = certificateChain[certificateChain.length - 1];
+
+ if (x509Cert.getKeyUsage()[0]) {
+ ++signingCount;
+ }
+
+ X500Principal subj = x509Cert.getSubjectX500Principal();
+ X500Principal issuer = x509Cert.getIssuerX500Principal();
+
+ String label = "'" + alias + "' (" + keyType + "): ";
+ if (now.compareTo(x509Cert.getNotAfter()) > 0) {
+ log.error(
+ pad + label + subj + ", issued by " + issuer +
+ ", S/N " + x509Cert.getSerialNumber().toString(16) + ", expired " +
+ x509Cert.getNotAfter() + " **EXPIRED**");
+ }
+ else {
+ log.info(
+ pad + label + subj + ", issued by " + issuer +
+ ", S/N " + x509Cert.getSerialNumber().toString(16) + ", expires " +
+ x509Cert.getNotAfter());
+ }
+
+ log.info(pad + "Key Usage: " + PKIUtils.formatKeyUsage(x509Cert));
+
+ boolean foundEntries = false;
+ try {
+ Collection> sanList = x509Cert.getSubjectAlternativeNames();
+ if (sanList != null) {
+ for (List> sanEntry : sanList) {
+ Integer type = (Integer) sanEntry.get(0);
+ Object value = sanEntry.get(1);
+ if (type == GeneralName.iPAddress || type == GeneralName.dNSName) {
+ if (!foundEntries) {
+ log.info(pad + "Subject Alternative Names:");
+ }
+ foundEntries = true;
+ log.info(pad + " " + value);
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ log.fatal("Error reading certificate SANs: " + e.getMessage());
+ System.exit(-1);
+ }
+
+ // Generally a server signing cert needs SAN entries
+ if (x509Cert.getKeyUsage()[0] && !foundEntries) {
+ log.warn(pad + "** No Hostname or IP Address SANs are defined **");
+ }
+ }
+ return signingCount;
+ }
private static String[] getEnabledTlsProtocols() {
String protocolList = System.getProperty(TLS_SERVER_PROTOCOLS_PROPERTY);
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java
index acf3a080c18..e8a5f2d532c 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java
@@ -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.
@@ -133,14 +133,6 @@ int getBlockSize() {
return blockSize;
}
- /**
- * Get the preferred socket send/receive buffer size to be used
- * @return preferred socket send/receive buffer size
- */
- protected int getPreferredBufferSize() {
- return (getBlockSize() + 4) * 12;
- }
-
/**
* Generate a random number for use as a block stream authentication token.
* @return random value
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java
index e18501ef831..f98e8b9d32d 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java
@@ -124,7 +124,6 @@ public int getBlockSize() {
public InputBlockStream openBlockStream() throws IOException {
Socket socket = connect();
- socket.setReceiveBufferSize(getPreferredBufferSize());
return new ClientInputBlockStream(socket);
}
@@ -135,8 +134,6 @@ void serveBlockStream(Socket socket, BlockStream blockStream) throws IOException
throw new IllegalArgumentException("expected InputBlockStream");
}
- socket.setSendBufferSize(getPreferredBufferSize());
-
InputBlockStream inputBlockStream = (InputBlockStream) blockStream;
try (OutputStream out = getBlockInputStream(socket)) {
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteOutputBlockStreamHandle.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteOutputBlockStreamHandle.java
index 9cc1a5b6e3c..ae83a34e943 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteOutputBlockStreamHandle.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteOutputBlockStreamHandle.java
@@ -109,7 +109,6 @@ public void writeBlock(BufferFileBlock block) throws IOException {
public OutputBlockStream openBlockStream() throws IOException {
Socket socket = connect();
- socket.setSendBufferSize(getPreferredBufferSize());
return new ClientOutputBlockStream(socket);
}
@@ -121,8 +120,6 @@ void serveBlockStream(Socket socket, BlockStream blockStream) throws IOException
throw new IllegalArgumentException("expected OutputBlockStream");
}
- socket.setReceiveBufferSize(getPreferredBufferSize());
-
OutputBlockStream outputBlockStream = (OutputBlockStream) blockStream;
try (InputStream in = getBlockInputStream(socket)) {
diff --git a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java b/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java
index 0ffcf0206ee..49e8f910d58 100644
--- a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java
+++ b/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java
@@ -41,8 +41,6 @@
//
// @category VxWorks
-import java.util.List;
-
import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.cmd.label.DemanglerCmd;
@@ -57,7 +55,6 @@
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
-import ghidra.program.model.util.CodeUnitInsertionException;
public class VxWorksSymTab_Finder extends GhidraScript {
@@ -323,7 +320,7 @@ private boolean isValidSymbolString(Address addr) {
return false;
}
- while (!SymbolUtilities.isInvalidChar((char) _byte) && _byte != 0x00) {
+ while (_byte != 0x00 && !SymbolUtilities.isInvalidCodePoint(Byte.toUnsignedInt(_byte))) {
if (monitor.isCancelled()) {
return false;
diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java
index cfaddaf4609..a5a1ca1a6ed 100644
--- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java
+++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb2/pdbreader/AbstractPdb.java
@@ -684,26 +684,37 @@ protected void deserializeParameters(PdbByteReader reader)
throws IOException, PdbException, CancelledException {
nameTable.deserializeDirectory(reader);
// Read the parameters.
+ // 20260616: Modified processing to prevent unabated growth of parameters. Will continue
+ // processing data from stream, looking only for expected parameter types. Loop will
+ // terminate upon repeated or unknown param signature (we will keep the first unknown
+ // parameter for diagnostic sake). The parameters list will only contain the values up to
+ // the point of termination. Since there are so few signatures that we are looking for,
+ // we are keeping parameters as a list and using the contains() method.
+ // If VC110_ID is found, then we terminate after capture, as no other parameters are
+ // supposed to be valid (here, we are now mimicking what is in the spec).
while (reader.hasMore()) {
checkCancelled();
- int val = reader.parseInt();
- parameters.add(val);
- }
- // Check the parameters for IDs
- for (int param : parameters) {
- checkCancelled();
- if (param == MINIMAL_DEBUG_INFO_PARAM) {
+ int param = reader.parseInt();
+ if (parameters.contains(param)) {
+ break;
+ }
+ parameters.add(param); // capture non-repeating param, which can be first "unknown"
+ if (param == PdbParser.VC110_ID) {
+ hasIdStream = true;
+ break; // no other parameters are supposed to be valid
+ }
+ else if (param == MINIMAL_DEBUG_INFO_PARAM) {
minimalDebugInfo = true;
}
else if (param == NO_TYPE_MERGE_PARAM) {
noTypeMerge = true;
}
- // Putting all of these >= ID after the specific == tests above
- // so that no >= tests in the ID section trigger off of any
- // of the above flags
- else if (param >= PdbParser.VC110_ID) {
+ else if (param == PdbParser.VC140_ID) {
hasIdStream = true;
}
+ else {
+ break;
+ }
}
}
diff --git a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py
index e8a3ebee7eb..a66118f9abb 100644
--- a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py
+++ b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/launcher.py
@@ -252,6 +252,8 @@ def _parse_dev_args(self) -> List[str]:
if "org.eclipse.jdt.launching.VM_ARGUMENTS" in line:
_, _, value = line.partition("value=")
value = value.removesuffix("/>")
+ if value.startswith('"') and value.endswith('"'):
+ value = value[1:-1]
return html.unescape(value).split()
raise Exception("org.eclipse.jdt.launching.VM_ARGUMENTS not found")
diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifExporter.java b/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifExporter.java
index 482f41b98fb..0138f42548c 100644
--- a/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifExporter.java
+++ b/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifExporter.java
@@ -20,8 +20,8 @@
import java.util.List;
import ghidra.app.util.*;
-import ghidra.app.util.exporter.Exporter;
import ghidra.app.util.exporter.ExporterException;
+import ghidra.app.util.exporter.ProgramExporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.AddressSetView;
@@ -36,7 +36,7 @@
* An implementation of exporter that creates
* an SARIF representation of the program.
*/
-public class SarifExporter extends Exporter {
+public class SarifExporter extends ProgramExporter {
private SarifProgramOptions options = new SarifProgramOptions();
/**
@@ -59,17 +59,21 @@ public void setOptions(List
options) throws OptionException {
this.options.setOptions(options);
}
+
@Override
public boolean export(File file, DomainObject domainObj, AddressSetView addrSet, TaskMonitor monitor)
throws IOException, ExporterException {
log.clear();
- if (!(domainObj instanceof Program)) {
- log.appendMsg("Unsupported type: "+domainObj.getClass().getName());
+ Program program;
+ try {
+ program = getProgram(domainObj);
+ }
+ catch (ClassCastException e) {
+ log.appendMsg("Unsupported type: " + domainObj.getClass().getName());
return false;
}
- Program program = (Program)domainObj;
if (addrSet == null) {
addrSet = program.getMemory();
diff --git a/Ghidra/Features/SwiftDemangler/README.md b/Ghidra/Features/SwiftDemangler/README.md
index 5801dc56e01..e8e34ce1282 100644
--- a/Ghidra/Features/SwiftDemangler/README.md
+++ b/Ghidra/Features/SwiftDemangler/README.md
@@ -1,7 +1,7 @@
# SwiftDemangler
-This module provides support for demanling mangled [Swift](https://www.swift.org) symbols. Supported
-mangled symbols begin with `$S`, `$s`, `_$S"`, `_$s`, or `_T`.
+This module provides support for demangling mangled [Swift](https://www.swift.org) symbols.
+Supported mangled symbols begin with `$S`, `$s`, `_$S"`, `_$s`, or `_T`.
The demangler currently relies on making direct calls to the native Swift demangler tool, which
comes [bundled with Swift](https://www.swift.org/download/). For example:
@@ -21,5 +21,4 @@ protocol descriptor for SwiftUI.View
The resulting tree is parsed by the Ghidra Swift Demangler to form and apply a demangled symbol
name.
-By default, the `Demangler Swift` Analyzer will search for the native Swift Demangler on the `PATH`.
-If it resides elsewhere, its path can be specified in the analyzer's options.
+The `Demangler Swift` Analyzer assumes that the native Swift Demangler is on the `PATH`.
diff --git a/Ghidra/Features/SwiftDemangler/ghidra_scripts/SwiftDemanglerScript.java b/Ghidra/Features/SwiftDemangler/ghidra_scripts/SwiftDemanglerScript.java
index 7dcdcb0ab41..abfa9911a35 100644
--- a/Ghidra/Features/SwiftDemangler/ghidra_scripts/SwiftDemanglerScript.java
+++ b/Ghidra/Features/SwiftDemangler/ghidra_scripts/SwiftDemanglerScript.java
@@ -55,7 +55,7 @@ protected void run() throws Exception {
return;
}
- SwiftNativeDemangler nativeDemangler = new SwiftNativeDemangler(options.getSwiftDir());
+ SwiftNativeDemangler nativeDemangler = new SwiftNativeDemangler();
SwiftNativeDemangledOutput demangledOutput = nativeDemangler.demangle(mangled);
println(demangledOutput.toString());
diff --git a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/plugin/core/analysis/SwiftDemanglerAnalyzer.java b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/plugin/core/analysis/SwiftDemanglerAnalyzer.java
index 48c58cd410e..aeb1beddf42 100644
--- a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/plugin/core/analysis/SwiftDemanglerAnalyzer.java
+++ b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/plugin/core/analysis/SwiftDemanglerAnalyzer.java
@@ -15,7 +15,6 @@
*/
package ghidra.app.plugin.core.analysis;
-import java.io.File;
import java.io.IOException;
import ghidra.app.util.demangler.*;
@@ -36,10 +35,7 @@ public class SwiftDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
private static final String NAME = "Demangler Swift";
private static final String DESCRIPTION =
- "Demangles Swift symbols and applies appropriate datatype and calling conventions where possible. Requires Swift to be installed.";
- private static final String OPTION_NAME_SWIFT_DIR = "Swift binary directory";
- private static final String OPTION_DESCRIPTION_SWIFT_DIR =
- "Path to the Swift installation binary directory, if not on PATH";
+ "Demangles Swift symbols and applies appropriate datatype and calling conventions where possible. Requires Swift to be on the PATH.";
private static final String OPTION_NAME_INCOMPLETE_PREFIX =
"Use incomplete demangle label prefix (%s)"
@@ -54,7 +50,6 @@ public class SwiftDemanglerAnalyzer extends AbstractDemanglerAnalyzer {
"Prefix unsupported demangled labels with '%s'"
.formatted(SwiftDemanglerOptions.UNSUPPORTED_PREFIX);
- private File swiftDir;
private boolean useIncompletePrefix = true;
private boolean useUnsupportedPrefix = true;
@@ -94,8 +89,6 @@ protected DemangledObject doDemangle(MangledContext mangledContext, MessageLog l
@Override
public void registerOptions(Options options, Program program) {
HelpLocation help = new HelpLocation("AutoAnalysisPlugin", "Demangler_Analyzer");
- options.registerOption(OPTION_NAME_SWIFT_DIR, OptionType.FILE_TYPE, swiftDir, help,
- OPTION_DESCRIPTION_SWIFT_DIR);
options.registerOption(OPTION_NAME_INCOMPLETE_PREFIX, OptionType.BOOLEAN_TYPE,
useIncompletePrefix, help, OPTION_DESCRIPTION_INCOMPLETE_PREFIX);
options.registerOption(OPTION_NAME_UNSUPPORTED_PREFIX, OptionType.BOOLEAN_TYPE,
@@ -104,9 +97,9 @@ public void registerOptions(Options options, Program program) {
@Override
protected boolean validateOptions(DemanglerOptions options, MessageLog log) {
- if (options instanceof SwiftDemanglerOptions swiftDemanglerOptions) {
+ if (options instanceof SwiftDemanglerOptions) {
try {
- new SwiftNativeDemangler(swiftDemanglerOptions.getSwiftDir());
+ new SwiftNativeDemangler();
return true;
}
catch (IOException e) {
@@ -120,7 +113,6 @@ protected boolean validateOptions(DemanglerOptions options, MessageLog log) {
@Override
public void optionsChanged(Options options, Program program) {
- swiftDir = options.getFile(OPTION_NAME_SWIFT_DIR, swiftDir);
useIncompletePrefix =
options.getBoolean(OPTION_NAME_INCOMPLETE_PREFIX, useIncompletePrefix);
useUnsupportedPrefix =
@@ -130,7 +122,6 @@ public void optionsChanged(Options options, Program program) {
@Override
protected DemanglerOptions getOptions() {
SwiftDemanglerOptions swiftDemanglerOptions = new SwiftDemanglerOptions();
- swiftDemanglerOptions.setSwiftDir(swiftDir);
swiftDemanglerOptions.setIncompletePrefix(useIncompletePrefix);
swiftDemanglerOptions.setUnsupportedPrefix(useUnsupportedPrefix);
return swiftDemanglerOptions;
diff --git a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemangler.java b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemangler.java
index d49cf4eb58e..589fbb3f55d 100644
--- a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemangler.java
+++ b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemangler.java
@@ -189,7 +189,7 @@ public SwiftDemanglerOptions getSwiftDemanglerOptions(DemanglerOptions opt)
private void setSwiftNativeDemangler(SwiftDemanglerOptions options) throws DemangledException {
if (nativeDemangler == null) {
try {
- nativeDemangler = new SwiftNativeDemangler(options.getSwiftDir());
+ nativeDemangler = new SwiftNativeDemangler();
}
catch (IOException e) {
throw new DemangledException(e);
diff --git a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemanglerOptions.java b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemanglerOptions.java
index 0f1ecff644f..be919119441 100644
--- a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemanglerOptions.java
+++ b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftDemanglerOptions.java
@@ -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.
@@ -15,8 +15,6 @@
*/
package ghidra.app.util.demangler.swift;
-import java.io.File;
-
import ghidra.app.util.demangler.DemanglerOptions;
/**
@@ -27,33 +25,9 @@ public class SwiftDemanglerOptions extends DemanglerOptions {
public static final String INCOMPLETE_PREFIX = "$";
public static final String UNSUPPORTED_PREFIX = "$$";
- private File swiftDir;
private boolean useIncompletePrefix;
private boolean useUnsupportedPrefix;
- /**
- * Gets the Swift directory
- *
- * If the Swift directory is on the PATH environment variable, this may return null
- *
- * @return The Swift directory
- */
- public File getSwiftDir() {
- return swiftDir;
- }
-
- /**
- * Sets the Swift directory
- *
- * If the Swift directory is on the PATH environment variable, it is fine to set this to
- * null
- *
- * @param swiftDir The Swift directory
- */
- public void setSwiftDir(File swiftDir) {
- this.swiftDir = swiftDir;
- }
-
/**
* {@return the "incomplete prefix" character to use in label names}
*/
diff --git a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftNativeDemangler.java b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftNativeDemangler.java
index f964e9a0b23..1fe4e40e362 100644
--- a/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftNativeDemangler.java
+++ b/Ghidra/Features/SwiftDemangler/src/main/java/ghidra/app/util/demangler/swift/SwiftNativeDemangler.java
@@ -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.
@@ -34,7 +34,7 @@
*/
public class SwiftNativeDemangler {
- private String nativeDemanglerPath;
+ private String nativeDemanglerCmd;
private boolean standaloneDemanglerBinary;
/**
@@ -54,25 +54,20 @@ public String toString() {
/**
* Creates a new {@link SwiftNativeDemangler}
*
- * @param swiftDir The Swift directory
* @throws IOException if there was a problem finding or running the Swift native demangler
*/
- public SwiftNativeDemangler(File swiftDir) throws IOException {
+ public SwiftNativeDemangler() throws IOException {
List demanglerNames = List.of("swift-demangle", "swift");
IOException ioe = null;
for (String demanglerName : demanglerNames) {
- nativeDemanglerPath = demanglerName;
- if (swiftDir != null) {
- nativeDemanglerPath = swiftDir + File.separator + nativeDemanglerPath;
- }
+ nativeDemanglerCmd = demanglerName; // expect swift demangler to be on the PATH
try {
int exitCode =
- new ProcessBuilder(List.of(nativeDemanglerPath, "--version")).start()
- .waitFor();
+ new ProcessBuilder(List.of(nativeDemanglerCmd, "--version")).start().waitFor();
if (exitCode == 0) {
ioe = null;
standaloneDemanglerBinary =
- new File(nativeDemanglerPath).getName().contains("-demangle");
+ new File(nativeDemanglerCmd).getName().contains("-demangle");
break;
}
ioe = new IOException("Native Swift demangler exited with code: " + exitCode);
@@ -144,7 +139,7 @@ public SwiftNativeDemangledOutput demangle(String mangled) throws IOException {
*/
private BufferedReader demangle(String mangled, List options) throws IOException {
List command = new ArrayList<>();
- command.add(nativeDemanglerPath);
+ command.add(nativeDemanglerCmd);
if (!standaloneDemanglerBinary) {
command.add("demangle");
}
diff --git a/Ghidra/Features/SwiftDemangler/src/test/java/ghidra/app/util/demangler/swift/SwiftDemanglerTest.java b/Ghidra/Features/SwiftDemangler/src/test/java/ghidra/app/util/demangler/swift/SwiftDemanglerTest.java
index 7ad9f33d952..a5e66b0a9d7 100644
--- a/Ghidra/Features/SwiftDemangler/src/test/java/ghidra/app/util/demangler/swift/SwiftDemanglerTest.java
+++ b/Ghidra/Features/SwiftDemangler/src/test/java/ghidra/app/util/demangler/swift/SwiftDemanglerTest.java
@@ -43,7 +43,7 @@ public void setUp() throws Exception {
// Ghidra does not ship the native Swift demangler binary, so it may not be present to run
// these tests. In this scenario, we just want these tests skipped (as opposed to failing).
try {
- new SwiftNativeDemangler(new SwiftDemanglerOptions().getSwiftDir());
+ new SwiftNativeDemangler();
}
catch (IOException e) {
assumeNoException(e); // skip test, don't fail
diff --git a/Ghidra/Framework/DB/src/main/java/db/DBHandle.java b/Ghidra/Framework/DB/src/main/java/db/DBHandle.java
index 6f86d7eea29..be6eaddc38b 100644
--- a/Ghidra/Framework/DB/src/main/java/db/DBHandle.java
+++ b/Ghidra/Framework/DB/src/main/java/db/DBHandle.java
@@ -46,6 +46,7 @@ public class DBHandle {
private long lastTransactionID;
private volatile boolean txStarted = false;
+ private volatile long txStartingModCount;
private boolean waitingForNewTransaction = false;
private boolean reloadInProgress = false;
@@ -472,6 +473,7 @@ public synchronized long startTransaction() {
}
waitingForNewTransaction = false;
txStarted = true;
+ txStartingModCount = getModCount();
return ++lastTransactionID;
}
@@ -509,7 +511,7 @@ public boolean endTransaction(long id, boolean commit) throws IOException {
*/
private synchronized boolean doEndTransaction(long id, boolean commit)
throws DBRollbackException, IOException {
- if (id != lastTransactionID) {
+ if (!txStarted || id != lastTransactionID) {
throw new IllegalStateException("Transaction id is not active");
}
try {
@@ -517,6 +519,7 @@ private synchronized boolean doEndTransaction(long id, boolean commit)
if (commit) {
masterTable.flush();
if (bufferMgr.checkpoint()) {
+ cachedChangedState = isChanged();
++checkpointNum;
return true;
}
@@ -531,12 +534,12 @@ private synchronized boolean doEndTransaction(long id, boolean commit)
}
finally {
txStarted = false;
- cacheChangedState();
}
return false;
}
private void cacheChangedState() {
+ // Must be invoked from within a synchronized block when no transaction is active
cachedChangedState = bufferMgr != null && bufferMgr.isChanged();
}
@@ -546,7 +549,10 @@ private void cacheChangedState() {
*
* @return current modification count
*/
- public long getModCount() {
+ public synchronized long getModCount() {
+ if (bufferMgr == null) {
+ return txStartingModCount; // ballpark value (database has been closed)
+ }
return bufferMgr.getModCount();
}
@@ -711,26 +717,6 @@ public int getTableCount() {
return tables.size();
}
- /**
- * Revert the current database version to an older version.
- * @param oldVersion
- * @param monitor
- * @return boolean
- * @throws IllegalStateException if the database has modified prior to
- * invoking this method.
- * @throws IllegalArgumentException if this method is invoked more than
- * once or the version file(s) are corrupt.
- */
-// boolean revert(int oldVersion, TaskMonitor monitor) throws IOException {
-// for (int v = (version-1); v >= oldVersion; --v) {
-// monitor.setMessage("Processing Version " + v);
-// bufferMgr.applyVersionFile(db.getVersionFile(v), monitor);
-// if (monitor.isCancelled())
-// return false;
-// }
-// return true;
-// }
-
/**
* Close the database and dispose of the underlying buffer manager.
* Any existing recovery data will be discarded.
@@ -762,11 +748,17 @@ public synchronized void close(boolean keepRecoveryData) {
/**
* Determine if the underlying database has changed.
- * NOTE: The returned value reflects a cached state assuming all underlaying database
- * transactions, saving, etc. are facilitated by this handle object.
+ *
+ * While a transaction is active this value will reflect changes made during the transaction
+ * which could ultimately never get committed. When a transaction is not active it is a
+ * reflection of the committed state.
+ *
* @return true if unsaved changes have been made.
*/
- public boolean isChanged() {
+ public synchronized boolean isChanged() {
+ if (bufferMgr != null && txStarted) {
+ return cachedChangedState || txStartingModCount != getModCount();
+ }
return cachedChangedState;
}
diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java b/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java
index 1d1251e2a01..c22add90bf7 100644
--- a/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java
+++ b/Ghidra/Framework/DB/src/main/java/db/buffers/LocalManagedBufferFile.java
@@ -93,10 +93,11 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu
/**
* bfMgr manages the various files associated with this buffer
* file. When working with versioned files or when Save support is
- * required bfMgr must be set. The bufMgr will be null for
- * a read-only non-updateable file.
+ * required bfMgr must be set. The openForUpdate will be false
+ * for a read-only non-updateable file.
*/
private BufferFileManager bfMgr;
+ private boolean openForUpdate = false;
/**
* checkinId is the checkin ID needed by bfMgr when a new
@@ -174,6 +175,7 @@ public LocalManagedBufferFile(int bufferSize, BufferFileManager bfManager, long
}
this.version = 1;
this.bfMgr = bfManager;
+ this.openForUpdate = true;
this.checkinId = checkinId;
}
@@ -191,6 +193,7 @@ public LocalManagedBufferFile(BufferFileManager bfManager, boolean versionUpdate
int minChangeDataVer, long checkinId) throws IOException {
super(bfManager.getBufferFile(bfManager.getCurrentVersion()), true);
this.bfMgr = bfManager;
+ this.openForUpdate = true;
this.version = bfManager.getCurrentVersion();
this.minChangeDataVer = minChangeDataVer;
this.checkinId = checkinId;
@@ -200,7 +203,7 @@ public LocalManagedBufferFile(BufferFileManager bfManager, boolean versionUpdate
}
/**
- * Open an older version of an existing buffer file as read-only and NOT UPDATEABLE (bfMgr remains null).
+ * Open an older version of an existing buffer file as read-only and NOT UPDATEABLE (openForUpdate remains false).
* Version files must exist for all versions starting with the requested version.
* These version files will be used in conjunction with the current buffer file
* to emulate an older version buffer file.
@@ -214,6 +217,7 @@ public LocalManagedBufferFile(BufferFileManager bfManager, boolean versionUpdate
public LocalManagedBufferFile(BufferFileManager bfManager, int version, int minChangeDataVer)
throws IOException {
super(bfManager.getBufferFile(bfManager.getCurrentVersion()), true);
+ this.bfMgr = bfManager;
this.version = version;
this.minChangeDataVer = minChangeDataVer;
int curVer = bfManager.getCurrentVersion();
@@ -243,9 +247,6 @@ private LocalManagedBufferFile(File presaveFile, int bufferSize) throws IOExcept
@Override
public BufferFile getNextChangeDataFile(boolean getFirst) throws IOException {
- if (bfMgr == null) {
- return null;
- }
if (getFirst || nextChangeDataVer == -1) {
nextChangeDataVer = minChangeDataVer != -1 ? minChangeDataVer : (version - 1);
}
@@ -291,9 +292,10 @@ public void setVersionComment(String comment) {
@Override
public synchronized DataBuffer get(DataBuffer buf, int index) throws IOException {
- if (index > getBufferCount())
+ if (index > getBufferCount()) {
throw new EOFException(
"Buffer index too large (" + index + " > " + getBufferCount() + ")");
+ }
if (versionFileHandler != null) {
DataBuffer vbuf = versionFileHandler.getOldBuffer(buf, index);
@@ -312,10 +314,12 @@ public synchronized DataBuffer get(DataBuffer buf, int index) throws IOException
@Override
public synchronized void put(DataBuffer buf, int index) throws IOException {
- if (isReadOnly())
+ if (isReadOnly()) {
throw new IOException("File is read-only");
- if (index > MAX_BUFFER_INDEX)
+ }
+ if (index > MAX_BUFFER_INDEX) {
throw new EOFException("Buffer index too large, exceeds max-int");
+ }
versionBufferIfNeeded(index);
@@ -355,8 +359,9 @@ private void versionBufferIfNeeded(int index) throws IOException {
@Override
public synchronized boolean setReadOnly() throws IOException {
- if (!flush())
+ if (!flush()) {
return false;
+ }
if (versionOutFile != null) {
versionOutFile.close();
@@ -369,8 +374,7 @@ public synchronized boolean setReadOnly() throws IOException {
super.setReadOnly();
- if (bfMgr != null) {
-// TODO: This seems very hidden!
+ if (openForUpdate) {
bfMgr.versionCreated(version, comment, checkinId);
startPreSave();
}
@@ -380,8 +384,9 @@ public synchronized boolean setReadOnly() throws IOException {
@Override
public synchronized void close() throws IOException {
- if (isClosed())
+ if (isClosed()) {
return;
+ }
stopPreSave(true);
@@ -406,7 +411,8 @@ public synchronized void close() throws IOException {
// NOTE: the above close will delete non-read-only files which were not committed
}
finally {
- if (bfMgr != null) {
+ if (openForUpdate) {
+ openForUpdate = false;
if (comit) {
bfMgr.versionCreated(version, comment, checkinId);
}
@@ -427,8 +433,9 @@ public synchronized void close() throws IOException {
@Override
public synchronized boolean delete() {
- if (isClosed() || isReadOnly())
+ if (isClosed() || isReadOnly()) {
return false;
+ }
boolean success = false;
try {
@@ -448,7 +455,8 @@ public synchronized boolean delete() {
}
}
finally {
- if (bfMgr != null) {
+ if (openForUpdate) {
+ openForUpdate = false;
bfMgr.updateEnded(checkinId);
}
}
@@ -456,9 +464,6 @@ public synchronized boolean delete() {
}
private byte[] getForwardModMapData() throws IOException {
- if (bfMgr == null) {
- return null;
- }
File mf = bfMgr.getChangeMapFile();
if (mf == null || !mf.exists()) {
return null;
@@ -474,9 +479,6 @@ private byte[] getForwardModMapData() throws IOException {
@Override
public byte[] getForwardModMapData(int oldVersion) throws IOException {
- if (bfMgr == null) {
- return null;
- }
if (oldVersion < 1 || oldVersion >= version) {
throw new IOException("Invalid mod-map version requested: " + oldVersion);
}
@@ -694,6 +696,7 @@ public synchronized void saveCompleted(boolean commit) throws IOException {
if (saveFile.renameFile(newFile)) {
saveFile.version = newVersion;
saveFile.bfMgr = bfMgr;
+ saveFile.openForUpdate = true;
saveFile.checkinId = checkinId;
if (saveChangeFile != null) {
@@ -712,6 +715,7 @@ public synchronized void saveCompleted(boolean commit) throws IOException {
if (!success) {
saveFile.delete();
}
+
saveFile = null;
saveChangeFile = null;
}
@@ -771,7 +775,7 @@ private void stopPreSave(boolean endUpdate) {
synchronized (this) {
// If preSaveFile is null and !preSaveFailed - we were unsuccessful at terminating the pre-save
- if (endUpdate && bfMgr != null && (preSaveFailed || preSaveFile != null)) {
+ if (endUpdate && openForUpdate && (preSaveFailed || preSaveFile != null)) {
// Update is ended when we can no longer perform a save
bfMgr.updateEnded(checkinId);
@@ -1066,15 +1070,20 @@ public synchronized void updateFrom(ManagedBufferFile versionedBufferFile, int o
success = true;
}
finally {
- saveCompleted(success);
- if (!success) {
- bfMgr.updateEnded(checkinId);
+ try {
+ saveCompleted(success);
+ }
+ finally {
+ bf.dispose();
+ if (!success) {
+ bfMgr.updateEnded(checkinId);
+ }
+// else {
+// // VERIFY RESULT FILE
+// System.err.println("Update check: " + file);
+// checkSameContent(versionedBufferFile, bf);
+// }
}
-// else {
-// // VERIFY RESULT FILE
-// System.err.println("Update check: " + file);
-// checkSameContent(versionedBufferFile, bf);
-// }
}
}
diff --git a/Ghidra/Framework/DB/src/test/java/db/DBTest.java b/Ghidra/Framework/DB/src/test/java/db/DBTest.java
index d3f0a619b37..a59d75ca865 100644
--- a/Ghidra/Framework/DB/src/test/java/db/DBTest.java
+++ b/Ghidra/Framework/DB/src/test/java/db/DBTest.java
@@ -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.
@@ -552,6 +552,37 @@ public void testTableWithIndexRecreateUndo() throws IOException {
}
+ @Test
+ public void testIsChanged() throws IOException {
+
+ assertFalse(dbh.isChanged());
+
+ long txId = dbh.startTransaction();
+ try {
+ assertFalse(dbh.isChanged());
+ }
+ finally {
+ dbh.endTransaction(txId, true);
+ }
+
+ assertFalse(dbh.isChanged());
+
+ txId = dbh.startTransaction();
+ try {
+ assertFalse(dbh.isChanged());
+
+ DBTestUtils.createLongKeyTable(dbh, "TABLE", DBTestUtils.SINGLE_LONG, false, false);
+ assertEquals(1, dbh.getTableCount());
+
+ assertTrue(dbh.isChanged());
+ }
+ finally {
+ dbh.endTransaction(txId, true);
+ }
+
+ assertTrue(dbh.isChanged());
+ }
+
@Test
public void testEvents() throws IOException {
diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties
index a8837843458..fcda9ff118f 100644
--- a/Ghidra/Framework/Docking/data/docking.theme.properties
+++ b/Ghidra/Framework/Docking/data/docking.theme.properties
@@ -108,6 +108,7 @@ icon.help.navigation.aid.enabled = software-update-available.png
icon.filechooser.default.directory = icon.folder.closed
icon.filechooser.default.file = [icon]laf.icon.FileView.fileIcon
+
icon.filechooser.places.my.computer = computer.png
icon.filechooser.places.desktop = desktop.png
icon.filechooser.places.home = user-home.png
@@ -214,3 +215,8 @@ color.bg.widget.tabs.selected.inactive = #696969 // dimgray
color.border.button.focused = [color]laf.color.TabbedPane.focus
+[Nimbus]
+
+// The Nimbus LaF makes a key for laf.icon.FileView.fileIcon, but we are not correctly finding it
+icon.filechooser.default.file = [icon]laf.icon.FileChooser.fileIcon
+
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java
index d4c7d15e526..bc2b4587973 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java
@@ -1515,9 +1515,24 @@ public static void triggerActionKey(Component c, int modifiers, int keyCode) {
*/
private static void forceTextComponentFocus(JTextComponent tc) {
- Object contextKey = getInstanceField("FOCUSED_COMPONENT", tc);
- AppContext context = AppContext.getAppContext();
- context.put(contextKey, tc);
+ runSwing(() -> {
+
+ // Update Swing's notion of the focused component
+ Object contextKey = getInstanceField("FOCUSED_COMPONENT", tc);
+ AppContext context = AppContext.getAppContext();
+ context.put(contextKey, tc);
+
+ /*
+ The FlatLaf will select all text in a text field when it gains focus. This will
+ break how we send key events to text fields. For text handling to work correctly,
+ we need to ensure that the given field has focus. If it gains focus in FlatLaf and
+ then selects the text, the next key event will overwrite the current text, which we
+ do not want.
+
+ See FlatClientProperties.SELECT_ALL_ON_FOCUS_POLICY
+ */
+ tc.putClientProperty("JTextField.selectAllOnFocusPolicy", "never");
+ });
}
/**
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleField.java
index 298384c14f5..cb6f2ec9a0d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleField.java
@@ -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.
@@ -19,6 +19,7 @@
import java.awt.event.FocusListener;
import java.text.BreakIterator;
import java.util.Locale;
+import java.util.Objects;
import javax.accessibility.*;
import javax.swing.JComponent;
@@ -49,11 +50,12 @@ public class AccessibleField extends AccessibleContext
* @param bounds the bounds of the field relative to the field panel.
*/
public AccessibleField(Field field, JComponent parent, int indexInParent, Rectangle bounds) {
+ Objects.requireNonNull(field);
this.field = field;
this.parent = parent;
this.indexInParent = indexInParent;
this.locale = parent.getLocale();
- this.boundsInParent = bounds;
+ this.boundsInParent = bounds != null ? bounds : new Rectangle(0, 0, 0, 0);
setAccessibleName("Field");
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleFieldPanelDelegate.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleFieldPanelDelegate.java
index 10b471cd516..303bfb2da86 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleFieldPanelDelegate.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/AccessibleFieldPanelDelegate.java
@@ -27,6 +27,7 @@
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.*;
+import ghidra.util.Msg;
/**
* Contains all the code for implementing the AccessibleFieldPanel which is an inner class in
@@ -217,8 +218,9 @@ public AccessibleField getAccessibleField(int fieldNum) {
*/
public AccessibleField getAccessibleField(FieldLocation loc) {
AccessibleLayout accessibleLayout = getAccessibleLayout(loc.getIndex());
+ int fieldNum = loc.getFieldNum();
if (accessibleLayout != null) {
- return getAccessibleField(accessibleLayout.getStartingFieldNum() + loc.getFieldNum());
+ return getAccessibleField(accessibleLayout.getStartingFieldNum() + fieldNum);
}
LayoutModel layoutModel = panel.getLayoutModel();
@@ -226,8 +228,12 @@ public AccessibleField getAccessibleField(FieldLocation loc) {
if (layout == null) {
return null;
}
- Field field = layout.getField(loc.getFieldNum());
- return new AccessibleField(field, panel, loc.getFieldNum(), null);
+ Field field = layout.getField(fieldNum);
+ if (field == null) {
+ Msg.warn(this, "Can't find field for given FieldLocation, loc = " + loc);
+ return null;
+ }
+ return new AccessibleField(field, panel, fieldNum, null);
}
private AccessibleLayout getAccessibleLayout(BigInteger index) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java
index 0cf7e290fc7..df23d6d4238 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileListCellRenderer.java
@@ -21,10 +21,7 @@
import javax.swing.JList;
-import org.bouncycastle.crypto.generators.DHBasicKeyPairGenerator;
-
import docking.widgets.list.GListCellRenderer;
-import ghidra.util.Msg;
import ghidra.util.filechooser.GhidraFileChooserModel;
class FileListCellRenderer extends GListCellRenderer {
@@ -43,26 +40,43 @@ protected String getItemText(File file) {
return chooser.getDisplayName(file);
}
+ @Override
+ public Dimension getPreferredSize() {
+ /*
+ The preferred size is used by the UI to pre-calculate the list's cell width to use while
+ rendering. The default preferred size does not account for the border insets. Some LaFs
+ appreciably change border size when focused. If the size of the cell is pre-calculated
+ using the smaller border size, then when the cell is focused text may be clipped, as the
+ cell size does not get updated when the border is changed. Start with the biggest known
+ border size to prevent clipping.
+ */
+ Dimension d = super.getPreferredSize();
+ int borderWidth = getMaxBorderWidth();
+ d.width += borderWidth;
+ return d;
+ }
+
@Override
public Component getListCellRendererComponent(JList extends File> list, File file, int index,
boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, file, index, isSelected, cellHasFocus);
+
setIcon(model.getIcon(file));
- // The file chooser's list will sometimes set a fixed width. When that happens, the text
- // may get clipped. When we get clipped text, add a tooltip to show the full text.
+ setToolTipText(null); // clear out previous cell's tool tip
+
+ // As a performance tweak, the file chooser's list will get set to a fixed width when the
+ // number of directory items is large. (Clients may also choose to set a fixed width value.)
+ // Setting a fixed width may cause a cell's text to get clipped. When we get clipped text,
+ // add a tooltip to show the full text.
int fixedWidth = list.getFixedCellWidth();
if (fixedWidth > 0) {
Dimension d = getPreferredSize();
if (d.getWidth() > fixedWidth) {
setToolTipText(getText());
- }
- else {
- setToolTipText(null);
}
}
-
return this;
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java
index 03f5ddddb70..c274b285900 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/list/GListCellRenderer.java
@@ -15,8 +15,7 @@
*/
package docking.widgets.list;
-import java.awt.Color;
-import java.awt.Component;
+import java.awt.*;
import java.util.function.Function;
import javax.swing.*;
@@ -38,7 +37,7 @@ public class GListCellRenderer extends AbstractGCellRenderer implements ListC
private static final Color LIST_BACKGROUND_COLOR = new GColor("color.bg.list.row");
/**
- * Returns a new ListCellRenderer that maps the list's data instance to a string used in the cell.
+ * Returns a new renderer that maps the list's data instance to a string used in the cell.
*
* Use this if you only need to provide a way to get the string value from the type being shown
* in the list.
@@ -56,20 +55,25 @@ protected String getItemText(E value) {
};
}
- /**
- * Constructs a new GListCellRenderer.
- */
public GListCellRenderer() {
// lists don't need alternation for rows, as they don't use long columnar data
setShouldAlternateRowBackgroundColors(false);
+ }
+ protected void initBorders() {
// Base our borders on those used by the list. ComboBoxes do not change the list borders in
// the Look and Feel.
noFocusBorder = getBorder("List.noFocusBorder");
focusBorder = getBorder("List.focusCellHighlightBorder");
}
+ @Override
+ public void updateUI() {
+ super.updateUI();
+ initBorders();
+ }
+
private Border getBorder(String id) {
Border border = UIManager.getBorder(id);
if (border == null) {
@@ -79,6 +83,25 @@ private Border getBorder(String id) {
return border;
}
+ /**
+ * Gets the max border size for the default borders used by this renderer. If any subclass or
+ * client uses borders other than 'noFocusBorder' or 'focusBorder', then they will need to
+ * override this method if any of their borders have bigger insets than the default borders of
+ * this class.
+ * @return the largest known border width
+ */
+ protected int getMaxBorderWidth() {
+ Insets insets = noFocusBorder.getBorderInsets(this);
+ int left = insets.left;
+ int right = insets.right;
+ int width = left + right;
+
+ insets = focusBorder.getBorderInsets(this);
+ left = Math.max(left, insets.left);
+ right = Math.max(right, insets.right);
+ return Math.max(width, left + right);
+ }
+
// overridden to return the list-specific background color
@Override
protected Color getDefaultBackgroundColor() {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/ColumnTypeMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/ColumnTypeMapper.java
index ee7afd82b5b..93c8cbfcd3c 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/ColumnTypeMapper.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/ColumnTypeMapper.java
@@ -19,7 +19,6 @@
import java.util.Objects;
import docking.widgets.table.constraint.provider.NumberColumnConstraintProvider;
-import generic.json.Json;
import ghidra.util.classfinder.ExtensionPoint;
import utilities.util.reflection.ReflectionUtilities;
@@ -62,7 +61,7 @@ protected ColumnTypeMapper(Class sourceType, Class destinationType) {
@Override
public String toString() {
- return Json.toString(this);
+ return "[source type=%s, destination type=%s]".formatted(sourceType, destinationType);
}
@Override
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/MappedColumnConstraint.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/MappedColumnConstraint.java
index 76c7b5ba621..c9208cd8e0b 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/MappedColumnConstraint.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/MappedColumnConstraint.java
@@ -17,7 +17,6 @@
import docking.widgets.table.constrainteditor.ColumnConstraintEditor;
import docking.widgets.table.constrainteditor.MappedColumnConstraintEditor;
-import generic.json.Json;
import ghidra.util.SystemUtilities;
/**
@@ -103,7 +102,7 @@ public ColumnConstraint getDelegate() {
@Override
public String toString() {
- return Json.toString(this);
+ return delegate.toString();
}
@Override
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterData.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterData.java
index 3f35ec71389..23f005153f4 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterData.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterData.java
@@ -17,8 +17,6 @@
import java.util.*;
-import org.apache.commons.collections4.CollectionUtils;
-
import docking.widgets.table.*;
import docking.widgets.table.constraint.ColumnConstraint;
import docking.widgets.table.constraint.ColumnTypeMapper;
@@ -168,15 +166,7 @@ public String getName() {
@Override
public String toString() {
- //@formatter:off
- return "{\n" +
- "\tname: " + name + ",\n" +
- "\tmodelColumn: " + modelIndex + ",\n" +
- "\tviewColumn: " + viewIndex + ",\n" +
- "\tconstraints: " +
- CollectionUtils.collect(applicableConstraints, c -> c.asString()) +"\n" +
- "}";
- //@formatter:on
+ return "[name=%s, model col=%s, view col=%s]".formatted(name, modelIndex, viewIndex);
}
/**
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterCondition.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterCondition.java
index cda6ca16ecf..6fe466dfd68 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterCondition.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterCondition.java
@@ -162,13 +162,7 @@ public void setValue(String valueString, Object dataSource) {
@Override
public String toString() {
- //@formatter:off
- return "{\n" +
- "\tname: " + constraintName + ",\n" +
- "\teditor: " + editor + "\n" +
-// "\tparent: " + parent + "\n" + // stack overflow when DialogFilterConditionSet.toString() is called
- "}";
- //@formatter:on
+ return "[name=%s, editor=%s]".formatted(constraintName, editor);
}
private ColumnConstraintEditor buildDummyEditor() {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterConditionSet.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterConditionSet.java
index 2af1564c98f..a73cbfafa89 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterConditionSet.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterConditionSet.java
@@ -168,13 +168,7 @@ private ColumnFilterData getColumnFilterData(int columnModelIndex) {
@Override
public String toString() {
- //@formatter:off
- return "{\n" +
-// "\tfilterRow: " + filterRow + ",\n" + // stack overflow when DialogFilterRow.toString() is called
- "\tdata: " + columnFilterData + ",\n" +
- "\tconditions: " + conditionSet +"\n" +
- "}";
- //@formatter:on
+ return "[data=%s, conditions=%s]".formatted(columnFilterData, conditionSet);
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterRow.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterRow.java
index d4dcde5a0ef..cea858e4ebd 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterRow.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/DialogFilterRow.java
@@ -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.
@@ -160,11 +160,6 @@ void delete() {
@Override
public String toString() {
- //@formatter:off
- return "{\n" +
- "\toperation: " + logicOperation + ",\n" +
- "\tconditions: " + columnConditionSet +"\n" +
- "}";
- //@formatter:on
+ return "[operations=%s, conditions=%s]".formatted(logicOperation, columnConditionSet);
}
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java
index 72219456459..9f708d1864e 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java
@@ -28,6 +28,7 @@
import generic.jar.ResourceFile;
import ghidra.framework.Application;
+import ghidra.util.Msg;
/**
* {@link GhidraObjectInputFilter} provides global serial input filter for use with Ghidra server
@@ -64,11 +65,24 @@ public class GhidraObjectInputFilter implements ObjectInputFilter {
private static final String MAXDEPTH = "maxdepth";
private static final String MAXBYTES = "maxbytes";
+ private static int getMaxArrayFromProperties() {
+ String limitStr = System.getProperty("ghidra.serial.array.limit", "200000");
+ try {
+ return Integer.parseInt(limitStr);
+ }
+ catch (Exception e) {
+ Msg.error(GhidraObjectInputFilter.class,
+ "Could not parse ghidra.serial.array.limit: %s. A decimal integer is required"
+ .formatted(limitStr));
+ }
+ return 200_000;
+ }
+
// NOTE: Be sure to update serialFilterREADME.md if values are updated.
- private int MAXARRAY_DEFAULT = 200_000;
- private int MAXREFS_DEFAULT = 10_000;
- private int MAXDEPTH_DEFAULT = 50;
- private int MAXBYTES_DEFAULT = 32 * 1024 * 1024; // 32MB
+ public static final int MAXARRAY_DEFAULT = getMaxArrayFromProperties();
+ public static final int MAXREFS_DEFAULT = 10_000;
+ public static final int MAXDEPTH_DEFAULT = 50;
+ public static final int MAXBYTES_DEFAULT = 32 * 1024 * 1024; // 32MB
private long maxArray;
private long maxRefs;
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FolderItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FolderItem.java
index 1aa48dee024..a2b2f0bec32 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FolderItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/FolderItem.java
@@ -276,8 +276,8 @@ ItemCheckoutStatus checkout(CheckoutType checkoutType, String user, String proje
/**
* Update the checkout version associated with this versioned item.
* @param checkoutId id corresponding to an existing checkout
- * @param checkoutVersion
- * @param user
+ * @param checkoutVersion current checkout version
+ * @param user user performing update
* @throws IOException if an IO error occurs.
*/
void updateCheckoutVersion(long checkoutId, int checkoutVersion, String user)
@@ -289,7 +289,7 @@ void updateCheckoutVersion(long checkoutId, int checkoutVersion, String user)
* @param version if this item is versioned, specifies the version to be output, otherwise
* -1 should be specified.
* @param monitor progress monitor
- * @throws IOException
+ * @throws IOException if failed to save packed file
* @throws CancelledException if monitor cancels operation
*/
public void output(File outputFile, int version, TaskMonitor monitor)
@@ -297,6 +297,8 @@ public void output(File outputFile, int version, TaskMonitor monitor)
/**
* Returns this instance after refresh or null if item no longer exists
+ * @return refreshed item
+ * @throws IOException if error occured during refresh
*/
public FolderItem refresh() throws IOException;
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/ItemDeserializer.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/ItemDeserializer.java
index 4499c350d20..1499711f93e 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/ItemDeserializer.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/ItemDeserializer.java
@@ -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.
@@ -15,15 +15,15 @@
*/
package ghidra.framework.store.local;
+import java.io.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
import generic.jar.ResourceFile;
import ghidra.util.MonitoredInputStream;
import ghidra.util.exception.IOCancelledException;
import ghidra.util.task.TaskMonitor;
-import java.io.*;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
/**
* ItemDeserializer facilitates the reading of a compressed data stream
* contained within a "packed" file. A "packed" file contains the following meta-data
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDataFileItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDataFileItem.java
index c7826dcdd54..8fecd3aa037 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDataFileItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDataFileItem.java
@@ -15,16 +15,16 @@
*/
package ghidra.framework.store.local;
+import java.io.*;
+
+import org.apache.commons.lang3.StringUtils;
+
import ghidra.framework.store.DataFileItem;
import ghidra.framework.store.FolderItem;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor;
-import java.io.*;
-
-import org.apache.commons.lang3.StringUtils;
-
/**
* LocalDataFileItem provides a FolderItem implementation
* for a local serialized data file. This implementation supports
@@ -186,8 +186,7 @@ void deleteCurrentVersion(String user) throws IOException {
@Override
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
- throw new UnsupportedOperationException("Output not yet supported for DataFiles");
-
+ throw new IOException("Packed output not yet supported for DataFiles");
}
@Override
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java
index fcabd092c69..a25b136c9f3 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java
@@ -600,13 +600,18 @@ public boolean canRecover() {
public void output(File outputFile, int version, TaskMonitor monitor)
throws CancelledException, IOException {
synchronized (fileSystem) {
+
+ String contentType = getContentType();
+ if (UnknownFolderItem.UNKNOWN_CONTENT_TYPE.equals(contentType)) {
+ throw new IOException("Unknown content type");
+ }
+
if (isVersioned) {
- versionedDb.output(version, outputFile, getName(), DATABASE_FILE_TYPE,
- getContentType(), monitor);
+ versionedDb.output(version, outputFile, getName(), DATABASE_FILE_TYPE, contentType,
+ monitor);
}
else {
- privateDb.output(outputFile, getName(), DATABASE_FILE_TYPE, getContentType(),
- monitor);
+ privateDb.output(outputFile, getName(), DATABASE_FILE_TYPE, contentType, monitor);
}
}
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFolderItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFolderItem.java
index 2a3f4195e2c..42d398519b9 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFolderItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalFolderItem.java
@@ -250,7 +250,7 @@ void beginCheckin(long checkoutId) throws FileInUseException {
throw new FileInUseException("Another checkin is in progress" + byMsg);
}
checkinId = checkoutId;
-//Log.put("Check-in started: " + checkinId);
+ //log.info("Check-in started: " + checkinId);
}
}
@@ -262,7 +262,7 @@ void endCheckin(long itemCheckinId) {
synchronized (fileSystem) {
if (this.checkinId == itemCheckinId) {
this.checkinId = DEFAULT_CHECKOUT_ID;
-//Log.put("Check-in ended: " + checkinId);
+ //log.info("Check-in ended: " + itemCheckinId);
}
}
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalTextDataItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalTextDataItem.java
index ecf7b61f44e..ab1be2b517c 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalTextDataItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalTextDataItem.java
@@ -117,7 +117,7 @@ void deleteCurrentVersion(String user) throws IOException {
@Override
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
- throw new IOException("Output not supported");
+ throw new IOException("Packed output not supported for text data");
}
@Override
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalUnknownFolderItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalUnknownFolderItem.java
index 716ed09732a..93daab7c7e3 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalUnknownFolderItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalUnknownFolderItem.java
@@ -115,7 +115,8 @@ void deleteCurrentVersion(String user) throws IOException {
@Override
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
- throw new UnsupportedOperationException("Output not supported for UnknownFolderItems");
+ throw new UnsupportedOperationException(
+ "Packed output not supported for UnknownFolderItems");
}
@Override
diff --git a/Ghidra/Framework/Generic/Module.manifest b/Ghidra/Framework/Generic/Module.manifest
index 37630b5f059..af96a293f19 100644
--- a/Ghidra/Framework/Generic/Module.manifest
+++ b/Ghidra/Framework/Generic/Module.manifest
@@ -3,8 +3,8 @@ MODULE FILE LICENSE: lib/commons-codec-1.18.0.jar Apache License 2.0
MODULE FILE LICENSE: lib/guava-32.1.3-jre.jar Apache License 2.0
MODULE FILE LICENSE: lib/failureaccess-1.0.1.jar Apache License 2.0
MODULE FILE LICENSE: lib/jdom2-2.0.6.1.jar JDOM License
-MODULE FILE LICENSE: lib/log4j-api-2.25.4.jar Apache License 2.0
-MODULE FILE LICENSE: lib/log4j-core-2.25.4.jar Apache License 2.0
+MODULE FILE LICENSE: lib/log4j-api-2.26.1.jar Apache License 2.0
+MODULE FILE LICENSE: lib/log4j-core-2.26.1.jar Apache License 2.0
MODULE FILE LICENSE: lib/commons-collections4-4.1.jar Apache License 2.0
MODULE FILE LICENSE: lib/commons-lang3-3.20.0.jar Apache License 2.0
MODULE FILE LICENSE: lib/commons-io-2.19.0.jar Apache License 2.0
diff --git a/Ghidra/Framework/Generic/build.gradle b/Ghidra/Framework/Generic/build.gradle
index ef03caa0565..4fa145d166e 100644
--- a/Ghidra/Framework/Generic/build.gradle
+++ b/Ghidra/Framework/Generic/build.gradle
@@ -34,8 +34,8 @@ dependencies {
}
api "com.google.guava:failureaccess:1.0.1"
api "org.jdom:jdom2:2.0.6.1"
- api "org.apache.logging.log4j:log4j-api:2.25.4"
- api "org.apache.logging.log4j:log4j-core:2.25.4"
+ api "org.apache.logging.log4j:log4j-api:2.26.1"
+ api "org.apache.logging.log4j:log4j-core:2.26.1"
api "org.apache.commons:commons-collections4:4.1"
api "org.apache.commons:commons-compress:1.27.1" // requires commons-codec
api "commons-codec:commons-codec:1.18.0"
diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java
index a8ca9b9b8cc..3d54c7a9ea1 100644
--- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java
+++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGTest.java
@@ -172,7 +172,7 @@ public static String getRandomString(int min, int max) {
int stringLength = getRandomInt(min, max);
StringBuilder buffy = new StringBuilder();
for (int i = 0; i < stringLength; i++) {
- buffy.append((char) getRandomInt(65, 127));
+ buffy.append((char) getRandomInt(65, 126));
}
return buffy.toString();
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java
index 1d270ed281a..0ae8b6d2848 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/DefaultKeyManagerFactory.java
@@ -385,7 +385,7 @@ private synchronized boolean init() throws CancelledException {
private synchronized boolean init(String newKeystorePath) throws CancelledException {
if (wrappedKeyManager != null) {
- if (StringUtils.equals(keystorePath, newKeystorePath)) {
+ if (Objects.equals(keystorePath, newKeystorePath)) {
return true;
}
invalidateKey();
@@ -393,7 +393,7 @@ private synchronized boolean init(String newKeystorePath) throws CancelledExcept
isSelfSigned = false;
try {
- if (newKeystorePath != null && newKeystorePath.length() != 0) {
+ if (!StringUtils.isBlank(newKeystorePath)) {
Msg.info(DefaultKeyManagerFactory.class,
"Using certificate keystore: " + newKeystorePath);
// Password optionally specified via property
@@ -411,9 +411,12 @@ else if (defaultIdentity != null) {
defaultSubjectAlternativeNames, pwd);
wrappedKeyManager = ApplicationKeyManagerFactory
.getKeyManagerFromKeyStore(selfSignedKeyStore, pwd);
+ keystorePath = null;
isSelfSigned = true;
}
else {
+ Msg.error(this,
+ "Failed to generate certificate without Distinguished Name (DN)");
return false;
}
return true;
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java
index d4e38868e67..551aa131fea 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/net/PKIUtils.java
@@ -565,4 +565,36 @@ static void logCert(String alias, X509Certificate x509Cert) {
x509Cert.getNotAfter());
}
}
+
+ private static final String[] KEY_USAGE_NAMES = {
+ "DigitalSignature",
+ "NonRepudiation",
+ "KeyEncipherment",
+ "DataEncipherment",
+ "KeyAgreement",
+ "KeyCertSign",
+ "CRLSign",
+ "EncipherOnly",
+ "DecipherOnly"
+ };
+
+ /**
+ * {@return the key-usages as sequence of comma-separated names for a specified certificate}
+ * @param cert x509 certificate
+ */
+ public static String formatKeyUsage(X509Certificate cert) {
+ boolean[] usage = cert.getKeyUsage();
+ if (usage == null) {
+ return "No KeyUsage extension present";
+ }
+
+ List enabled = new ArrayList<>();
+ for (int i = 0; i < usage.length && i < KEY_USAGE_NAMES.length; i++) {
+ if (usage[i]) {
+ enabled.add(KEY_USAGE_NAMES[i]);
+ }
+ }
+
+ return String.join(", ", enabled);
+ }
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ObjectStorageStreamAdapter.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ObjectStorageStreamAdapter.java
index 9010b861aeb..9a8bc990930 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/ObjectStorageStreamAdapter.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/ObjectStorageStreamAdapter.java
@@ -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.
@@ -18,376 +18,454 @@
import java.io.*;
/**
- * Implementation for ObjectStorage to save and restore Strings and
- * Java primitives using an ObjectOutputStream and ObjectInputStream,
- * respectively.
+ * Implementation for ObjectStorage to save and restore Strings and Java primitives using an
+ * ObjectOutputStream and ObjectInputStream, respectively.
*
*
*/
public class ObjectStorageStreamAdapter implements ObjectStorage {
+
+ private static int getMaxArrayFromProperties() {
+ String limitStr = System.getProperty("ghidra.serial.array.limit", "200000");
+ try {
+ return Integer.parseInt(limitStr);
+ }
+ catch (Exception e) {
+ Msg.error(ObjectStorageStreamAdapter.class,
+ "Could not parse ghidra.serial.array.limit: %s. A decimal integer is required"
+ .formatted(limitStr));
+ }
+ return 200_000;
+ }
+
+ public static final int MAXARRAY = getMaxArrayFromProperties();
+
ObjectOutputStream out;
ObjectInputStream in;
- /**
- * Constructor for ObjectStorageStreamAdapter.
- * @param out output stream to write to
- */
- public ObjectStorageStreamAdapter(ObjectOutputStream out) {
- this.out = out;
- }
- /**
- * Constructor for new ObjectStorageStreamAdapter
- * @param in input stream to read from
- */
- public ObjectStorageStreamAdapter(ObjectInputStream in) {
- this.in = in;
- }
- @Override
- public void putInt(int value) {
- try {
- out.writeInt(value);
- } catch (IOException e) {}
- }
+ /**
+ * Constructor for ObjectStorageStreamAdapter.
+ *
+ * @param out output stream to write to
+ */
+ public ObjectStorageStreamAdapter(ObjectOutputStream out) {
+ this.out = out;
+ }
+
+ /**
+ * Constructor for new ObjectStorageStreamAdapter
+ *
+ * @param in input stream to read from
+ */
+ public ObjectStorageStreamAdapter(ObjectInputStream in) {
+ this.in = in;
+ }
@Override
- public void putByte(byte value) {
- try {
- out.writeByte(value);
- } catch (IOException e) {}
- }
+ public void putInt(int value) {
+ try {
+ out.writeInt(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putShort(short value) {
- try {
- out.writeShort(value);
- } catch (IOException e) {}
- }
+ public void putByte(byte value) {
+ try {
+ out.writeByte(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putLong(long value) {
- try {
- out.writeLong(value);
- } catch (IOException e) {}
- }
+ public void putShort(short value) {
+ try {
+ out.writeShort(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putString(String value) {
- try {
- out.writeObject(value);
- } catch (IOException e) {}
- }
+ public void putLong(long value) {
+ try {
+ out.writeLong(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putBoolean(boolean value) {
- try {
- out.writeBoolean(value);
- } catch (IOException e) {}
- }
+ public void putString(String value) {
+ try {
+ out.writeObject(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putFloat(float value) {
- try {
- out.writeFloat(value);
- } catch (IOException e) {}
- }
+ public void putBoolean(boolean value) {
+ try {
+ out.writeBoolean(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putDouble(double value) {
- try {
- out.writeDouble(value);
- } catch (IOException e) {}
- }
+ public void putFloat(float value) {
+ try {
+ out.writeFloat(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public int getInt() {
- try {
- return in.readInt();
- } catch (IOException e) {
- return 0;
- }
- }
+ public void putDouble(double value) {
+ try {
+ out.writeDouble(value);
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public byte getByte() {
- try {
- return in.readByte();
- } catch (IOException e) {
- return (byte)0;
- }
- }
+ public int getInt() {
+ try {
+ return in.readInt();
+ }
+ catch (IOException e) {
+ return 0;
+ }
+ }
@Override
- public short getShort() {
- try {
- return in.readShort();
- } catch (IOException e) {
- return (short)0;
- }
- }
+ public byte getByte() {
+ try {
+ return in.readByte();
+ }
+ catch (IOException e) {
+ return (byte) 0;
+ }
+ }
@Override
- public long getLong() {
- try {
- return in.readLong();
- } catch (IOException e) {
- return 0;
- }
- }
+ public short getShort() {
+ try {
+ return in.readShort();
+ }
+ catch (IOException e) {
+ return (short) 0;
+ }
+ }
@Override
- public boolean getBoolean() {
- try {
- return in.readBoolean();
- } catch (IOException e) {
- return false;
- }
- }
+ public long getLong() {
+ try {
+ return in.readLong();
+ }
+ catch (IOException e) {
+ return 0;
+ }
+ }
@Override
- public String getString() {
- try {
- return (String)in.readObject();
- }catch(Exception e) {
- return null;
- }
- }
+ public boolean getBoolean() {
+ try {
+ return in.readBoolean();
+ }
+ catch (IOException e) {
+ return false;
+ }
+ }
@Override
- public float getFloat() {
- try {
- return in.readFloat();
- } catch (IOException e) {
- return 0;
- }
- }
+ public String getString() {
+ try {
+ return (String) in.readObject();
+ }
+ catch (Exception e) {
+ return null;
+ }
+ }
@Override
- public double getDouble() {
- try {
- return in.readDouble();
- } catch (IOException e) {
- return 0.0;
- }
- }
+ public float getFloat() {
+ try {
+ return in.readFloat();
+ }
+ catch (IOException e) {
+ return 0;
+ }
+ }
@Override
- public void putInts(int[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeInt(value[i]);
- }
- } catch (IOException e) {}
- }
+ public double getDouble() {
+ try {
+ return in.readDouble();
+ }
+ catch (IOException e) {
+ return 0.0;
+ }
+ }
+
+ protected void checkWriteArrayLength(int n) throws IOException {
+ if (n > MAXARRAY) {
+ throw new IOException("Array size %d exceeds max of %d".formatted(n, MAXARRAY));
+ }
+ out.writeInt(n);
+ }
@Override
- public void putBytes(byte[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeByte(value[i]);
- }
- } catch (IOException e) {}
- }
+ public void putInts(int[] value) {
+ try {
+ if (value == null) {
+ out.writeInt(-1);
+ return;
+ }
+ checkWriteArrayLength(value.length);
+ for (int i = 0; i < value.length; i++) {
+ out.writeInt(value[i]);
+ }
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putShorts(short[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeShort(value[i]);
- }
- } catch (IOException e) {}
-
- }
+ public void putBytes(byte[] value) {
+ try {
+ if (value == null) {
+ out.writeInt(-1);
+ return;
+ }
+ checkWriteArrayLength(value.length);
+ for (int i = 0; i < value.length; i++) {
+ out.writeByte(value[i]);
+ }
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public void putLongs(long[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeLong(value[i]);
- }
- } catch (IOException e) {}
-
- }
+ public void putShorts(short[] value) {
+ try {
+ if (value == null) {
+ out.writeInt(-1);
+ return;
+ }
+ checkWriteArrayLength(value.length);
+ for (int i = 0; i < value.length; i++) {
+ out.writeShort(value[i]);
+ }
+ }
+ catch (IOException e) {
+ }
+
+ }
@Override
- public void putFloats(float[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeFloat(value[i]);
- }
- } catch (IOException e) {}
-
- }
+ public void putLongs(long[] value) {
+ try {
+ if (value == null) {
+ out.writeInt(-1);
+ return;
+ }
+ checkWriteArrayLength(value.length);
+ for (int i = 0; i < value.length; i++) {
+ out.writeLong(value[i]);
+ }
+ }
+ catch (IOException e) {
+ }
+
+ }
@Override
- public void putDoubles(double[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeDouble(value[i]);
- }
- } catch (IOException e) {}
- }
+ public void putFloats(float[] value) {
+ try {
+ if (value == null) {
+ out.writeInt(-1);
+ return;
+ }
+ checkWriteArrayLength(value.length);
+ for (int i = 0; i < value.length; i++) {
+ out.writeFloat(value[i]);
+ }
+ }
+ catch (IOException e) {
+ }
+
+ }
@Override
- public void putStrings(String[] value) {
- try {
- if (value == null) {
- out.writeInt(-1);
- return;
- }
- out.writeInt(value.length);
- for (int i = 0; i < value.length; i++) {
- out.writeObject(value[i]);
- }
- } catch (IOException e) {}
- }
+ public void putDoubles(double[] value) {
+ try {
+ if (value == null) {
+ out.writeInt(-1);
+ return;
+ }
+ checkWriteArrayLength(value.length);
+ for (int i = 0; i < value.length; i++) {
+ out.writeDouble(value[i]);
+ }
+ }
+ catch (IOException e) {
+ }
+ }
@Override
- public int[] getInts() {
- try {
- int n = in.readInt();
- if (n < 0) {
- return null;
- }
- int[] r = new int[n];
- for(int i=0;i MAXARRAY) {
+ throw new IOException("Array size %d exceeds max of %d".formatted(n, MAXARRAY));
+ }
+ return n;
+ }
@Override
- public byte[] getBytes() {
- try {
- int n = in.readInt();
- if (n < 0) {
- return null;
- }
- byte[] r = new byte[n];
- for(int i=0;i constructor = found.getConstructor();
- assertEquals(Canary.ALIVE, canary);
TestSuper instance = constructor.newInstance();
assertNotNull(instance);
assertEquals(Canary.DEAD, canary);
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java
index b0bd4244829..ce4aee0b1f3 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java
@@ -1263,11 +1263,9 @@ public synchronized boolean isDisposed() {
@Override
public void close() {
synchronized (this) {
- if (!closed) {
- closed = true;
- }
+ closed = true;
if (inUseCount != 0) {
- return; // delay dispose
+ return; // delay dispose while still in-use
}
}
dispose();
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java
index 337c68d9798..3dc3db83caa 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java
@@ -656,6 +656,13 @@ DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToR
DomainFileProxy proxy = new DomainFileProxy(name, parent.getPathname(), doa,
DomainFile.DEFAULT_VERSION, fileID, parent.getProjectLocator());
proxy.setLastModified(getLastModifiedTime());
+
+ // Notify file manager of in-use domain object.
+ // A link-file object is indirect with tracking intiated by the URL-referenced file.
+ if (!isLink()) {
+ projectData.trackDomainFileInUse(doa);
+ }
+
return doa;
}
myFolderItem = folderItem;
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DefaultLaunchMode.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DefaultLaunchMode.java
index f8cbbc18840..3a0ced246ca 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DefaultLaunchMode.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DefaultLaunchMode.java
@@ -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.
@@ -23,20 +23,20 @@
*/
public enum DefaultLaunchMode {
- REUSE_TOOL("Reuse acceptable running tool"),
- NEW_TOOL("Launch new default tool");
+ REUSE_TOOL("Reuse Tool"),
+ NEW_TOOL("New Tool");
public static DefaultLaunchMode DEFAULT = NEW_TOOL;
- private String str;
+ private String description;
private DefaultLaunchMode(String str) {
- this.str = str;
+ this.description = str;
}
@Override
public String toString() {
- return str;
+ return description;
}
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java
index 9255d26daa9..9cd11908424 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java
@@ -158,7 +158,7 @@ public interface DomainObject {
* Saves (i.e., serializes) the current content to a packed file.
* @param outputFile packed output file
* @param monitor progress monitor
- * @throws IOException if an exception occurs
+ * @throws IOException if an error occurs during operation
* @throws CancelledException if the user cancels
* @throws UnsupportedOperationException if not supported by object implementation
*/
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProjectManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProjectManager.java
index 5a8071f01aa..fdee732bb25 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProjectManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProjectManager.java
@@ -568,7 +568,7 @@ private void populateProjectURLList(List list, String propertyName) {
/**
* Update preferences file with list of known projects.
*/
- void updatePreferences() {
+ protected void updatePreferences() {
setProjectLocatorProperty(recentlyOpenedProjectsList, RECENT_PROJECTS);
setProjectURLProperty(recentlyViewedProjectsList, VIEWED_PROJECTS);
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 90f2eb785f7..57996f9afed 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
@@ -16,7 +16,6 @@
package ghidra.framework.protocol.ghidra;
import java.io.File;
-import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.Objects;
import java.util.regex.Matcher;
@@ -47,6 +46,13 @@
*
* Various system path utilities are also provided in support of local Ghidra project URLs and
* {@link ProjectLocator}.
+ *
+ * NOTE: Avoid using {@link URL#getRef()} which returns the encoded refereance fragment as it
+ * appears in the URL. The proper technique is to use {@code URL.toURI().getFragment()}.
+ * Alternatively, the method {@link #getDecodedReference(URL)} may be used. Encoding of space
+ * character in URL reference can be inconsistent. URI encodes space as {@code %20} while
+ * some other implementation may encode space as '+'. Creation and decoding of Ghidra URLs
+ * should use this utility class to ensure proper round-trip encode/decode.
*/
public class GhidraURL {
@@ -265,40 +271,21 @@ else if (uri.getAuthority() != null) {
}
/**
- * {@return Get the URL-decoded reference/fragment from the URL or null}
+ * {@return Get the URL-decoded reference fragment from the URL or null}
*
- * NOTE: The presence of "+" in the original reference fragment is problematic and
- * requires consistent use of this method in conjunction with the URL instantiation
- * methods provided by this utility class.
+ * NOTE: Avoid using {@link URL#getRef()} which returns the encoded refereance fragment as it
+ * appears in the URL.
*
- * @param url Ghidra URL
+ * @param url URL or null if no ref or URL failure occurs
*/
public static String getDecodedReference(URL url) {
- String ref = url.getRef();
- if (StringUtils.isBlank(ref)) {
- return null;
- }
try {
- // NOTE: original "+" may appear encoded in final URL as "%252B"
- ref = URLDecoder.decode(ref, "UTF-8");
- ref = ref.replace("%2B", "+"); // force double-decode of original "+"
- return ref;
+ return url.toURI().getFragment();
}
- catch (UnsupportedEncodingException e) {
- return null;
+ catch (URISyntaxException e) {
+ // ignore
}
- }
-
- /**
- * Perform preliminary encode of '+' within raw ref string so that it may be preserved
- * and properly decoded from {@link URI#getFragment()} by {@link #getDecodedReference(URL)}.
- * Once fully encoded by URI a raw '+' will appear with a double encoding of "%252B".
- *
- * @param rawRef raw ref/fragment to be prepared for use with URI creation.
- * @return preliminary encoding of specified raw ref string.
- */
- private static String encodeRefPlus(String rawRef) {
- return StringUtils.isBlank(rawRef) ? null : rawRef.replace("+", "%2B");
+ return null;
}
/**
@@ -310,10 +297,15 @@ private static String encodeRefPlus(String rawRef) {
* @return repository name or null if not applicable to URL
*/
public static String getRepositoryName(URL url) {
- if (!isServerURL(url)) {
+
+ if (isLocalURL(url)) {
return null;
}
+ if (!isServerURL(url)) {
+ throw new IllegalArgumentException("Invalid project/repository URL");
+ }
+
try {
URI uri = url.toURI();
String path = getServerURIPath(uri);
@@ -453,6 +445,12 @@ private static void checkValidProjectPath(String path, int startIndex) {
/**
* Create a Ghidra URL from a string form of a Ghidra URL or local project path.
* This method can consume strings produced by the getDisplayString method.
+ *
+ * NOTE: If a Ghidra URL string is improperly encode (e.g., had been made by simple string
+ * concatentaion) an exception may be thrown. In such situations, a URL re-encoding will be
+ * attempted but may produce an incorrect URL if partial encoding was used. The following
+ * characters present in an improperly encoded URL may not handled properly:
+ * {@code ' ', '?', '%', '+', '&', '#', '=' }
*
* @param projectPathOrURL {@literal project path (/)} or
* string form of Ghidra URL.
@@ -478,12 +476,42 @@ public static URL toURL(String projectPathOrURL) {
return makeURL(location, projectName);
}
- // NOTE: We must assume URL is properly encoded in its external form
+ URL url;
try {
- return URI.create(projectPathOrURL).toURL();
+ // NOTE: will fail if URL has improper encoding
+ url = URI.create(projectPathOrURL).toURL();
}
catch (Exception e) {
- throw new IllegalArgumentException("Invalid Ghidra URL", e);
+ // Since URL string may have been improperly formed via simple string concatentenation
+ // attempt to repair encodes (primarily handles presence of spaces).
+ url = getReformedGhidraURL(projectPathOrURL);
+ if (url == null) {
+ throw new IllegalArgumentException("Invalid Ghidra URL: " + projectPathOrURL, e);
+ }
+ }
+ if (!isLocalURL(url) && url.getQuery() != null) {
+ throw new IllegalArgumentException("Invalid Ghidra URL: " + projectPathOrURL);
+ }
+ return url;
+ }
+
+ private static URL getReformedGhidraURL(String invalidGhidraUrl) {
+
+ if (!invalidGhidraUrl.startsWith(PROTOCOL_URL_START + "/")) {
+ // Assume opaque ghidra URL (i.e., ghidra protocol extension)
+ return null;
+ }
+
+ try {
+ // Assume old code was used to directly for URL without the use of URI
+ URL url = new URL(invalidGhidraUrl);
+
+ URI repairedUri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(),
+ url.getPort(), url.getPath(), url.getQuery(), url.getRef());
+ return repairedUri.toURL();
+ }
+ catch (Exception e) {
+ return null;
}
}
@@ -513,8 +541,6 @@ public static URL resolve(URL ghidraUrl, String projectFilePath, String ref) {
projectFilePath = null;
}
- ref = encodeRefPlus(ref);
-
Exception exc = null;
try {
URI uri = ghidraUrl.toURI();
@@ -691,6 +717,10 @@ public static URL getNormalizedURL(URL url) {
return new URI(PROTOCOL, ssp, null).toURL();
}
+ if (uri.getQuery() != null) {
+ throw new URISyntaxException(url.toExternalForm(), "Invalid Ghidra URL");
+ }
+
String host = uri.getHost();
String revisedHost = getHostAsIpAddress(host);
if (Objects.equals(host, revisedHost) && url.getRef() == null) {
@@ -818,7 +848,7 @@ public static URL makeURL(String projectLocation, String projectName, String pro
}
try {
- return new URI(GhidraURL.PROTOCOL, null, path, projectFilePath, encodeRefPlus(ref))
+ return new URI(GhidraURL.PROTOCOL, null, path, projectFilePath, ref)
.toURL();
}
catch (URISyntaxException | MalformedURLException e) {
@@ -937,7 +967,7 @@ public static URL makeURL(String host, int port, String repositoryName, String r
}
try {
- return new URI(PROTOCOL, null, host, port, path, null, encodeRefPlus(ref)).toURL();
+ return new URI(PROTOCOL, null, host, port, path, null, ref).toURL();
}
catch (URISyntaxException | MalformedURLException e) {
throw new IllegalArgumentException(e);
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java
index 23665815e72..b0f0d58af67 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java
@@ -70,8 +70,8 @@ public static boolean isSupportedURL(URL url) {
}
if (url.getAuthority() != null) {
- // assume standard ghidra URL (ghidra://...)
- return true;
+ // assume standard ghidra URL (ghidra://...) - query not allowed
+ return url.getQuery() == null;
}
try {
return getProtocolExtensionHandler(url) != null;
diff --git a/Ghidra/Framework/Project/src/test/java/ghidra/framework/protocol/ghidra/GhidraURLTest.java b/Ghidra/Framework/Project/src/test/java/ghidra/framework/protocol/ghidra/GhidraURLTest.java
index 72bbed1ba55..c2833b75373 100644
--- a/Ghidra/Framework/Project/src/test/java/ghidra/framework/protocol/ghidra/GhidraURLTest.java
+++ b/Ghidra/Framework/Project/src/test/java/ghidra/framework/protocol/ghidra/GhidraURLTest.java
@@ -607,6 +607,60 @@ public void testIsServerURL() throws Exception {
assertTrue(GhidraURL.isServerURL(ghidraUrl));
}
+ @Test
+ public void testBadServerURL() throws Exception {
+
+ try {
+ GhidraURL.toURL("ghidra://localhost:123/Test/foo/bar?/a/b#ref%20+123");
+ fail("Expected failure due to invalid query");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ URL url = URI.create("ghidra://localhost:123/Test/foo/bar?/a/b#ref%20+123").toURL();
+
+ try {
+ GhidraURL.getRepositoryName(url);
+ fail("Expected failure due to invalid query");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ GhidraURL.getFolderURL(url);
+ fail("Expected failure due to invalid query");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ GhidraURL.getNormalizedURL(url);
+ fail("Expected failure due to invalid query");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ GhidraURL.getProjectPathname(url);
+ fail("Expected failure due to invalid query");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ GhidraURL.getProjectURL(url);
+ fail("Expected failure due to invalid query");
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
// toURL(String)
@Test
public void testToURL() throws Exception {
@@ -641,17 +695,32 @@ public void testToURL() throws Exception {
assertEquals(ghidraUrl, GhidraURL.toURL(ghidraUrl.toString()));
assertEquals(ghidraUrl, GhidraURL.toURL(GhidraURL.getDisplayString(ghidraUrl)));
- ghidraUrl = GhidraURL.makeURL("/x/y", "Test", "/a/", "ref");
- assertEquals(ghidraUrl, GhidraURL.toURL("ghidra:/x/y/Test?/a/#ref"));
+ ghidraUrl = GhidraURL.makeURL("/x/y", "Test", "/a/", "ref +123");
+ assertEquals("ref%20+123", ghidraUrl.getRef());
+ assertEquals("ref +123", ghidraUrl.toURI().getFragment());
+
+ assertEquals(ghidraUrl, GhidraURL.toURL("ghidra:/x/y/Test?/a/#ref%20+123"));
assertEquals(ghidraUrl, GhidraURL.toURL(ghidraUrl.toString()));
assertEquals(ghidraUrl, GhidraURL.toURL(GhidraURL.getDisplayString(ghidraUrl)));
- assertEquals("ref", GhidraURL.getDecodedReference(ghidraUrl));
+ assertEquals("ref +123", GhidraURL.getDecodedReference(ghidraUrl));
+
+ ghidraUrl = GhidraURL.makeURL("localhost", 123, "Test", "/foo", "bar", "ref +123");
+ assertEquals("ref%20+123", ghidraUrl.getRef());
+ assertEquals("ref +123", ghidraUrl.toURI().getFragment());
- ghidraUrl = GhidraURL.makeURL("localhost", 123, "Test", "/foo", "bar", "ref+123");
- // GhidraURL.toURL requires external URL form with double-encoding for '+' in ref
- assertEquals(ghidraUrl, GhidraURL.toURL("ghidra://localhost:123/Test/foo/bar#ref%252B123"));
+ assertEquals(ghidraUrl, GhidraURL.toURL("ghidra://localhost:123/Test/foo/bar#ref%20+123"));
assertEquals(ghidraUrl, GhidraURL.toURL(ghidraUrl.toString()));
assertEquals(ghidraUrl, GhidraURL.toURL(GhidraURL.getDisplayString(ghidraUrl)));
+ assertEquals("ref +123", GhidraURL.getDecodedReference(ghidraUrl));
+
+ ghidraUrl = GhidraURL.makeURL("localhost", 123, "Test", "/foo", "bar", "ref++123");
+ assertEquals("ref++123", ghidraUrl.getRef());
+ assertEquals("ref++123", ghidraUrl.toURI().getFragment());
+
+ assertEquals(ghidraUrl, GhidraURL.toURL("ghidra://localhost:123/Test/foo/bar#ref++123"));
+ assertEquals(ghidraUrl, GhidraURL.toURL(ghidraUrl.toString()));
+ assertEquals(ghidraUrl, GhidraURL.toURL(GhidraURL.getDisplayString(ghidraUrl)));
+ assertEquals("ref++123", GhidraURL.getDecodedReference(ghidraUrl));
// Unicode foreign language example
ghidraUrl = GhidraURL.makeURL("localhost", 123, "Gr\u00FCnerTee", "/\u6771\u4EAC/",
@@ -662,6 +731,33 @@ public void testToURL() throws Exception {
assertEquals(ghidraUrl, GhidraURL.toURL(GhidraURL.getDisplayString(ghidraUrl)));
}
+ @Test
+ public void testRelaxedToURL() throws Exception {
+ String path = "/Test/my foo/bar";
+ String improperUrl = "ghidra://127.0.0.1:123" + path + "#a b+c";
+ URL ghidraUrl = GhidraURL.toURL(improperUrl);
+
+ assertEquals("a b+c", GhidraURL.getDecodedReference(ghidraUrl));
+ assertEquals("Test", GhidraURL.getRepositoryName(ghidraUrl));
+ assertEquals("/my foo/bar", GhidraURL.getProjectPathname(ghidraUrl));
+
+ assertEquals(path.replace(" ", "%20"), ghidraUrl.getPath());
+ assertEquals(null, ghidraUrl.getQuery());
+ assertEquals("a%20b+c", ghidraUrl.getRef());
+
+ improperUrl = "ghidra:" + path + "?" + path + "#a b+c";
+ ghidraUrl = GhidraURL.toURL(improperUrl);
+
+ assertEquals("a b+c", GhidraURL.getDecodedReference(ghidraUrl));
+ assertEquals(path + ".rep",
+ GhidraURL.getProjectStorageLocator(ghidraUrl).getProjectDir().toString());
+ assertEquals(path, GhidraURL.getProjectPathname(ghidraUrl));
+
+ assertEquals(path.replace(" ", "%20"), ghidraUrl.getPath());
+ assertEquals(path.replace(" ", "%20"), ghidraUrl.getQuery());
+ assertEquals("a%20b+c", ghidraUrl.getRef());
+ }
+
@Test
public void testGetProjectURL() throws Exception {
@@ -846,9 +942,6 @@ private URL toGhidraLocalURL(String path, String projectFilePath)
private URL toGhidraLocalURL(String path, String projectFilePath, String ref)
throws MalformedURLException, URISyntaxException {
- if (ref != null) {
- ref = ref.replace("+", "%2B"); // force encoding of "+"
- }
return new URI(GhidraURL.PROTOCOL, null, path, projectFilePath, ref).toURL();
}
@@ -866,9 +959,6 @@ private URL toGhidraServerURL(String host, int port, String repo, String path, S
if (path != null) {
repoAndPath += path;
}
- if (ref != null) {
- ref = ref.replace("+", "%2B"); // force encoding of "+"
- }
return new URI(GhidraURL.PROTOCOL, null, host, port, repoAndPath, null, ref).toURL();
}
}
diff --git a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/ShellUtils.java b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/ShellUtils.java
index 9d853cc5282..d4d26d22366 100644
--- a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/ShellUtils.java
+++ b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/ShellUtils.java
@@ -19,11 +19,28 @@
import java.util.*;
import java.util.stream.Collectors;
+import ghidra.framework.OperatingSystem;
+import ghidra.framework.Platform;
+
public class ShellUtils {
enum State {
NORMAL, NORMAL_ESCAPE, DQUOTE, DQUOTE_ESCAPE, SQUOTE, SQUOTE_ESCAPE;
}
+ /**
+ * Parse a command line into an argument list
+ *
+ * LATER: This is meant to mimic UNIX- / C-style arguments, but that's probably not appropriate
+ * on all systems. We should either:
+ *
+ *
+ *
Let the offer specify how @args ought to be treated.
+ *
Let the header annotations for @args include some extra specifier.
+ *
+ *
+ * @param args the arguments as a single string
+ * @return the list of split arguments
+ */
public static List parseArgs(String args) {
List argsList = new ArrayList<>();
StringBuilder curArg = new StringBuilder();
@@ -123,29 +140,205 @@ public static List removePath(List args) {
return List.copyOf(copy);
}
- public static String generateLine(List args) {
+ public static String generateLine(List args, Shell shell) {
if (args.isEmpty()) {
return "";
}
- StringBuilder line = new StringBuilder(generateArgument(args.get(0)));
+ StringBuilder line = new StringBuilder(shell.generateArgument(args.get(0)));
for (int i = 1; i < args.size(); i++) {
String a = args.get(i);
- line.append(" " + generateArgument(a));
+ line.append(" " + shell.generateArgument(a));
}
return line.toString();
}
- public static String generateArgument(String a) {
- if (a.contains(" ")) {
- if (a.contains("\"")) {
- if (a.contains("'")) {
- return "\"" + a.replace("\"", "\\\"") + "\"";
+ /**
+ * A target shell for command-line arguments
+ *
+ * This determines how arguments are quoted and/or escaped. This should be set based on the
+ * shell that is going to receive the actual commands, which may or may not be the local shell.
+ * In many cases, it is the local shell, but please ensure for remote cases, the correct shell
+ * is specified.
+ */
+ public enum Shell {
+ /**
+ * For display purposes only. DO NOT pass to any actual shell.
+ */
+ DISPLAY {
+ @Override
+ public String generateArgument(String a) {
+ if (a.contains(" ")) {
+ if (a.contains("\"")) {
+ if (a.contains("'")) {
+ return '"' + a.replace("\"", "\\\"") + '"';
+ }
+ return "'" + a + "'";
+ }
+ return '"' + a + '"';
+ }
+ return a;
+ }
+ },
+ /**
+ * Unix shells that follow the same conventions as "sh". This is most Unix shells.
+ */
+ UNIX_SH {
+ @Override
+ public String generateArgument(String a) {
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < a.length(); i++) {
+ char c = a.charAt(i);
+ boolean esc = switch (c) {
+ case '\t', ' ', // Whitespace
+ '&', '|', ';', '`', '(', ')', // Syntax, command separators
+ '<', '>', // Redirection
+ '$', // Variable substitution
+ '#', // Comments
+ '[', ']', '?', '*', // File globbing
+ '"', '\'', // Quotes
+ '\\' // The escape character itself
+ -> true;
+ default -> false;
+ };
+ if (esc) {
+ b.append('\\');
+ }
+ b.append(c);
}
- return "'" + a + "'";
+ return b.toString();
}
- return "\"" + a + "\"";
+ },
+ /**
+ * Plain Windows command-line arguments for the C runtime
+ *
+ * @see CommandLineToArgvWfunction
+ */
+ WINDOWS {
+ /**
+ * Derived from Everyone
+ * quotes command line arguments the wrong way
+ *
+ * The section titled "The Correct Solution" invites its readers to "translate it into
+ * your language and coding style off choice."
+ */
+ @Override
+ public String generateArgument(String a) {
+ if (!a.isEmpty() && a.indexOf(' ') == -1 && a.indexOf('\t') == -1 &&
+ a.indexOf('\n') == -1 && a.indexOf('"') == -1) {
+ return a;
+ }
+ StringBuilder b = new StringBuilder();
+ b.append('"');
+ for (int i = 0;; i++) {
+ int nBackSlash = 0;
+ while (i < a.length() && a.charAt(i) == '\\') {
+ i++;
+ nBackSlash++;
+ }
+
+ if (i == a.length()) {
+ /**
+ * We reached the end of the argument while counting backslashes. Escape
+ * them all. The terminating " we add at the end of this method will be
+ * interpreted as a metacharacter.
+ */
+ b.append("\\".repeat(nBackSlash * 2));
+ break;
+ }
+ else if (a.charAt(i) == '"') {
+ /**
+ * Sequence of backslashes ends in a ". Escape them all, including the ".
+ */
+ b.append("\\".repeat(nBackSlash * 2 + 1));
+ b.append(a.charAt(i));
+ }
+ else {
+ /**
+ * They're just literal backslashes. Do not escape them. Be sure to add the
+ * current character, too.
+ */
+ b.append("\\".repeat(nBackSlash)); // No *2
+ b.append(a.charAt(i));
+ }
+ }
+ b.append('"');
+ return b.toString();
+ }
+ },
+ /**
+ * The Windows cmd.exe shell.
+ *
+ * NOTE: It seems to me using this with {@link ShellUtils#generateLine(List, Shell)}
+ * is futile, if the intent is to use specific argument numbers in the batch file, e.g.,
+ * %1. If you make clear certain constraints to the user, maybe it's suitable,
+ * but especially involving quotes, it's not possible to encode any arbitrary string. It
+ * seems the cmd shell is primarily concerned with just passing the arguments along to child
+ * processes, as encoded, and then the child figures out the parsing. That said, if the
+ * child process parses to argc/argv, then so long as the full command line is
+ * passed through the batch file, it should work as intended. However, if grabbing
+ * individual arguments, they cannot be reliably controlled.
+ *
+ * LATER: There may be a way to factor the escaping part separately from the argument
+ * catenation part, so that special logic can be applied here to better guarantee argument
+ * numbering, but then there's still the issue if the final target is expected to parse to
+ * argc/argv, if that can be encoded reliably.
+ */
+ WINDOWS_CMD {
+ @Override
+ public String generateArgument(String a) {
+ String quoted = WINDOWS.generateArgument(a);
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < quoted.length(); i++) {
+ char c = quoted.charAt(i);
+ /**
+ * The list and rationale for each metacharacter comes from the same blogpost:
+ * Everyone
+ * quotes command line arguments the wrong way
+ */
+ boolean esc = switch (c) {
+ case '(', ')', '%', '!', '<', '>', '&', '|', // Metacharacters
+ '"', // Prevent cmd from interpreting quotes
+ '^' // The escape character itself
+ -> true;
+ default -> false;
+ };
+ if (esc) {
+ b.append('^');
+ }
+ b.append(c);
+ }
+ return b.toString();
+ }
+ };
+
+ /**
+ * Get the probable shell for the given operating system
+ *
+ * @param os the operating system
+ * @return the shell, probably
+ */
+ public static Shell forOs(OperatingSystem os) {
+ return switch (os) {
+ case OperatingSystem.WINDOWS -> Shell.WINDOWS;
+ default -> Shell.UNIX_SH;
+ };
}
- return a;
+
+ /**
+ * The local shell, probably
+ */
+ public static final Shell LOCAL = forOs(Platform.CURRENT_PLATFORM.getOperatingSystem());
+
+ /**
+ * Escape and/or quote a single command-line argument
+ *
+ * @param a the argument
+ * @return the argument formed in such a way that the shell will interpret it as the given
+ * string in one argument
+ */
+ public abstract String generateArgument(String a);
}
public static String generateEnvBlock(Map env) {
diff --git a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java
index cbccbc6a7dc..3ab7de73a33 100644
--- a/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java
+++ b/Ghidra/Framework/Pty/src/main/java/ghidra/pty/windows/ConPtyChild.java
@@ -28,6 +28,7 @@
import ghidra.pty.PtyChild;
import ghidra.pty.ShellUtils;
+import ghidra.pty.ShellUtils.Shell;
import ghidra.pty.local.LocalWindowsNativeProcessPtySession;
import ghidra.pty.windows.jna.ConsoleApiNative;
import ghidra.pty.windows.jna.ConsoleApiNative.STARTUPINFOEX;
@@ -75,12 +76,51 @@ protected STARTUPINFOEX prepareStartupInfo() {
return si;
}
+ private boolean isImplicitCmd(String[] args) {
+ if (args.length < 1) {
+ return false; // Really shouldn't, but let Windows decide how to fail
+ }
+ String lower0 = args[0].toLowerCase();
+ if (lower0.endsWith(".bat") || lower0.endsWith(".cmd")) {
+ return true;
+ }
+ /**
+ * I'm on the fence about this. While it's pretty clear that invoking a .bat file, which
+ * implicitly prefixes cmd /c, ought to escape the metacharacters, I'm not
+ * certain about when a user explicitly invokes cmd /c. I think it should let
+ * the metacharacters through, i.e., cmd should be permitted to do what the user probably
+ * intended. Still, if someone using this API unwittingly puts the cmd /c
+ * prefix on a user-supplied command line without sanitizing, they could create a
+ * vulnerability.
+ */
+ /*if (args.length < 2) {
+ return false;
+ }
+ if (!"/c".equals(args[1])) {
+ return false;
+ }
+ if ("cmd".equals(lower0) || "cmd.exe".equals(lower0) || lower0.endsWith("\\cmd") ||
+ lower0.endsWith("\\cmd.exe")) {
+ return true;
+ }*/
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * WARNING: If arg[0], i.e., the application name, is a batch file, Windows will
+ * automatically invoke it using cmd /c. This method is aware of this implicit
+ * invocation and, upon detecting it, will appropriately escape cmd's metacharacters.
+ * However, if a client explicitly invokes cmd /c with any part of the
+ * command line formed from user-supplied arguments, IT MUST sanitize those arguments itself.
+ * This can be achieved using {@link Shell#generateArgument(String)} of
+ * {@link Shell#WINDOWS_CMD}.
+ */
@Override
public LocalWindowsNativeProcessPtySession session(String[] args, Map env,
File workingDirectory, Collection mode) throws IOException {
/**
- * TODO: How to incorporate environment into CreateProcess?
- *
* TODO: How to control local echo?
*/
@@ -92,7 +132,8 @@ public LocalWindowsNativeProcessPtySession session(String[] args, Map 1) {
- mod = displayOffset % unitSize;
+ mod = Long.remainderUnsigned(displayOffset, unitSize);
displayOffset = addrSpace.getAddressableWordOffset(displayOffset);
}
@@ -287,7 +287,7 @@ else if (showAddressSpace) {
buf.append(addressString);
if (mod != 0) {
buf.append('.');
- buf.append(mod);
+ buf.append(Long.toHexString(mod));
}
if (stackFormat) {
buf.append(STACK_ADDRESS_SUFFIX);
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java
index 1326596935a..c2ce47e033e 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java
@@ -437,6 +437,8 @@ else if (languageVersionExc != null) {
final Language lang = language;
final CompilerSpec cspec = compilerSpec;
+
+ // Use ProgramAddressFactory to provide stack space
final AddressFactory addrFactory = new ProgramAddressFactory(lang, cspec, s -> null);
super.setProgramArchitecture(new ProgramArchitecture() {
@@ -733,6 +735,10 @@ else if (updateOption == LanguageUpdateOption.TRANSLATE) {
}
}
+ // Use ProgramAddressFactory to provide stack space
+ final AddressFactory addrFactory =
+ new ProgramAddressFactory(language, compilerSpec, s -> null);
+
ProgramArchitecture programArchitecture = new ProgramArchitecture() {
@Override
@@ -747,7 +753,7 @@ public CompilerSpec getCompilerSpec() {
@Override
public AddressFactory getAddressFactory() {
- return language.getAddressFactory();
+ return addrFactory;
}
};
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Encoder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Encoder.java
index 45f085bd33e..15273c60419 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Encoder.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Encoder.java
@@ -138,4 +138,12 @@ public interface Encoder {
* @throws IOException for errors in the underlying stream
*/
void writeOpcode(AttributeId attribId, OpCode opcode) throws IOException;
+
+ /**
+ * Write a p-code operation opcode into the encoding, given the opcode ordinal, defined in {@link PcodeOp}
+ * @param attribId is the attribute to associate with the opcode
+ * @param opcode is the opcode ordinal
+ * @throws IOException for errors in the underlying stream
+ */
+ void writeOpcode(AttributeId attribId, int opcode) throws IOException;
}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PackedEncode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PackedEncode.java
index d2fbf89ee1c..c5e4f14ce57 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PackedEncode.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PackedEncode.java
@@ -207,6 +207,12 @@ public void writeOpcode(AttributeId attribId, OpCode opcode) throws IOException
writeInteger((TYPECODE_SIGNEDINT_POSITIVE << TYPECODE_SHIFT), opcode.ordinal());
}
+ @Override
+ public void writeOpcode(AttributeId attribId, int opcode) throws IOException {
+ writeHeader(ATTRIBUTE, attribId.id());
+ writeInteger((TYPECODE_SIGNEDINT_POSITIVE << TYPECODE_SHIFT), opcode);
+ }
+
/**
* @return the underlying stream
*/
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java
index e89815450ba..ba657b0883f 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeOp.java
@@ -439,7 +439,7 @@ public final void setOutput(Varnode vn) {
*/
public void encodeRaw(Encoder encoder, AddressFactory addrFactory) throws IOException {
encoder.openElement(ELEM_OP);
- encoder.writeSignedInteger(ATTRIB_CODE, opcode);
+ encoder.writeOpcode(ATTRIB_CODE, opcode);
encoder.writeSignedInteger(ATTRIB_SIZE, input.length);
if (output == null) {
encoder.openElement(ELEM_VOID);
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/XmlEncode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/XmlEncode.java
index c74da2042ee..7809b2eadbc 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/XmlEncode.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/XmlEncode.java
@@ -15,7 +15,7 @@
*/
package ghidra.program.model.pcode;
-import static ghidra.program.model.pcode.AttributeId.ATTRIB_CONTENT;
+import static ghidra.program.model.pcode.AttributeId.*;
import java.io.IOException;
import java.io.OutputStream;
@@ -250,6 +250,11 @@ public void writeOpcode(AttributeId attribId, OpCode opcode) throws IOException
buffer.append("\"");
}
+ @Override
+ public void writeOpcode(AttributeId attribId, int opcode) throws IOException {
+ writeOpcode(attribId, OpCode.getOpcode(opcode));
+ }
+
@Override
public void writeTo(OutputStream stream) throws IOException {
byte[] res = buffer.toString().getBytes();
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java
index 76975364c8a..edc9d5c6fbf 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java
@@ -98,11 +98,6 @@ public class SymbolUtilities {
*/
public final static String ORDINAL_PREFIX = "Ordinal_";
- /**
- * Invalid characters for a symbol name.
- */
- public final static char[] INVALIDCHARS = { ' ' };
-
private static final Comparator CASE_INSENSITIVE_SYMBOL_NAME_COMPARATOR = (s1, s2) -> {
return s1.getName().compareToIgnoreCase(s2.getName());
};
@@ -135,24 +130,23 @@ public static int getOrdinalValue(String symbolName) {
}
/**
- * Check for invalid characters
- * (space or unprintable ascii below 0x20)
- * in labels.
+ * Checks a string for invalid characters (control chars, space chars, whitespace chars).
*
* @param str the string to be checked for invalid characters.
- * @return boolean true if no invalid chars
+ * @return boolean true if the string has invalid chars, false if string is valid
*/
public static boolean containsInvalidChars(String str) {
- int len = str.length();
- for (int i = 0; i < len; i++) {
- char c = str.charAt(i);
- if (isInvalidChar(c)) {
+ for (int i = 0; i < str.length();) {
+ int codePoint = str.codePointAt(i);
+ if (isInvalidCodePoint(codePoint)) {
return true;
}
+ i += Character.charCount(codePoint);
}
return false;
}
+
/**
* Generates a default function name for a given address.
* @param addr the entry point of the function.
@@ -329,54 +323,135 @@ private static boolean isHexDigit(char c) {
}
/**
- * Returns true if the specified char
- * is not valid for use in a symbol name
+ * Returns true if the specified char is not valid for use in a symbol name.
+ *
+ * See {@link #isInvalidCodePoint(int)} for better method that uses code points instead of
+ * chars.
+ *
* @param c the character to be tested as a valid symbol character.
- * @return return true if c is an invalid char within a symbol name, else false
+ * @return boolean true if c is an invalid char within a symbol name, else false
*/
public static boolean isInvalidChar(char c) {
- if (c < ' ') { // non-printable ASCII
- return true;
- }
+ return isInvalidCodePoint(c);
+ }
- for (char element : INVALIDCHARS) {
- if (c == element) {
+ /**
+ * Returns true if the specified code point is not valid for use in a symbol name.
+ *
+ * @param cp the code point to be tested as a valid symbol character.
+ * @return boolean true if the code point is an invalid character within a symbol name,
+ * else false
+ */
+ public static boolean isInvalidCodePoint(int cp) {
+ // Invisible / unprintable / whitespace unicode character categories.
+ // This bad list + the good list in the following comment are an exhaustive list of all
+ // unicode categories
+ switch (Character.getType(cp)) {
+ case Character.SPACE_SEPARATOR:
+ case Character.COMBINING_SPACING_MARK:
+ case Character.CONTROL:
+ case Character.ENCLOSING_MARK:
+ case Character.FORMAT:
+ case Character.LINE_SEPARATOR:
+ case Character.NON_SPACING_MARK:
+ case Character.PARAGRAPH_SEPARATOR:
+ case Character.PRIVATE_USE:
+ case Character.SURROGATE:
+ case Character.UNASSIGNED:
return true;
- }
+ /*
+ Unicode Character categories that are allowed:
+ Character.UPPERCASE_LETTER
+ Character.LOWERCASE_LETTER
+ Character.TITLECASE_LETTER
+ Character.MODIFIER_LETTER
+ Character.OTHER_LETTER
+ Character.DECIMAL_DIGIT_NUMBER
+ Character.LETTER_NUMBER
+ Character.OTHER_NUMBER
+ Character.DASH_PUNCTUATION
+ Character.START_PUNCTUATION
+ Character.END_PUNCTUATION
+ Character.CONNECTOR_PUNCTUATION
+ Character.OTHER_PUNCTUATION
+ Character.MATH_SYMBOL
+ Character.CURRENCY_SYMBOL
+ Character.MODIFIER_SYMBOL
+ Character.OTHER_SYMBOL
+ Character.INITIAL_QUOTE_PUNCTUATION
+ Character.FINAL_QUOTE_PUNCTUATION
+ */
}
return false;
}
/**
- * Removes from the given string any invalid characters or replaces
- * them with underscores.
- *
- * For example:
- * given "a:b*c", the return value would be "a_b_c"
- *
- * @param str the string to have invalid chars converted to underscores or removed.
- * @param replaceWithUnderscore - true means replace the invalid
- * chars with underscore. if false, then just drop the invalid chars
- * @return modified string
+ * Callback functional interface, called by
+ * {@link SymbolUtilities#replaceInvalidChars(String, BadCharFixupFunc)} when it encounters a
+ * bad code point that needs addressing. (good characters in a string are NOT sent to
+ * this method)
+ */
+ public interface BadCharFixupFunc {
+ String fixBadChar(int origIndex, int badCodePoint);
+ }
+
+ /**
+ * BadCharFixupFunc that replaces bad characters with '_' underscores
+ */
+ public static final BadCharFixupFunc USE_UNDERSCORES = (i, cp) -> "_";
+ /**
+ * BadCharFixupFunc that removes bad characters from the string
+ */
+ public static final BadCharFixupFunc OMIT_BAD_CHARS = (i, cp) -> null;
+
+ /**
+ * Converts a string with possible invalid characters into a valid symbol string.
+ *
+ * @param str String to fix, {@code null} ok
+ * @param replaceWithUnderscore - true means replace the invalid chars with underscores, else
+ * if false, then just drop the invalid chars
+ * @return either the original String instance if already valid (or {@code null}), or a new
+ * string that contains the valid portions of the original with any bad chars removed or
+ * replaced with underscores.
*/
public static String replaceInvalidChars(String str, boolean replaceWithUnderscore) {
+ return replaceInvalidChars(str, replaceWithUnderscore ? USE_UNDERSCORES : OMIT_BAD_CHARS);
+ }
+
+ /**
+ * Converts a string with possible invalid characters into a valid symbol string.
+ *
+ * @param str String to fix, {@code null} ok
+ * @param badCharFixup callback that controls how each bad char is fixed. It should return
+ * a string that should be used in place of the invalid character, or {@code null} if nothing
+ * should be used.
+ * @return either the original String instance if already valid (or {@code null}), or a new
+ * string that contains the valid portions of the original with any fixed-ups as returned by
+ * the badCharFixup callback.
+ */
+ public static String replaceInvalidChars(String str, BadCharFixupFunc badCharFixup) {
if (str == null) {
return null;
}
- int len = str.length();
- StringBuilder buf = new StringBuilder(len);
- for (int i = 0; i < len; ++i) {
- char c = str.charAt(i);
- if (isInvalidChar(c)) {
- if (replaceWithUnderscore) {
- buf.append(UNDERSCORE);
+ StringBuilder result = null;
+ for (int i = 0; i < str.length();) {
+ int codePoint = str.codePointAt(i);
+ if (isInvalidCodePoint(codePoint)) {
+ if (result == null) {
+ result = new StringBuilder(str.length());
+ result.append(str.substring(0, i));
+ }
+ String replacement = badCharFixup.fixBadChar(i, codePoint);
+ if (replacement != null) {
+ result.append(replacement);
}
}
- else {
- buf.append(c);
+ else if (result != null) {
+ result.appendCodePoint(codePoint);
}
+ i += Character.charCount(codePoint);
}
- return buf.toString();
+ return result != null ? result.toString() : str;
}
/**
diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SparcAssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SparcAssemblyTest.java
index 522b5e24e47..f68ac148322 100644
--- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SparcAssemblyTest.java
+++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SparcAssemblyTest.java
@@ -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.
@@ -27,7 +27,7 @@ protected LanguageID getLanguageID() {
@Test
public void testAssemble_rd_Y_g2() {
- assertOneCompatRestExact("rd %Y,g2", "85:40:00:00");
+ assertOneCompatRestExact("rd %Y,%g2", "85:40:00:00");
}
// Too many reserved/undefined bits: 524288 instructions to test
@@ -38,6 +38,6 @@ public void testAssemble_restore() {
@Test
public void testAssemble_wr_g3_g0() {
- assertOneCompatRestExact("wr g0,g3,%Y", "81:80:00:03");
+ assertOneCompatRestExact("wr %g0,%g3,%Y", "81:80:00:03");
}
}
diff --git a/Ghidra/Processors/68000/certification.manifest b/Ghidra/Processors/68000/certification.manifest
index 55d58490e3a..caf51979e76 100644
--- a/Ghidra/Processors/68000/certification.manifest
+++ b/Ghidra/Processors/68000/certification.manifest
@@ -12,6 +12,7 @@ data/languages/68020.slaspec||GHIDRA||reviewed||END|
data/languages/68030.slaspec||GHIDRA||||END|
data/languages/68040.slaspec||GHIDRA||||END|
data/languages/coldfire.slaspec||GHIDRA||||END|
+data/languages/CPU32.slaspec||GHIDRA||||END|
data/manuals/68000.idx||GHIDRA||||END|
data/patterns/68000_patterns.xml||GHIDRA||||END|
data/patterns/patternconstraints.xml||GHIDRA||||END|
diff --git a/Ghidra/Processors/68000/data/languages/68000.ldefs b/Ghidra/Processors/68000/data/languages/68000.ldefs
index 5115f2d878b..7fe13fc6bf4 100644
--- a/Ghidra/Processors/68000/data/languages/68000.ldefs
+++ b/Ghidra/Processors/68000/data/languages/68000.ldefs
@@ -73,4 +73,17 @@
+
+ Motorola 32-bit CPU32 (MC68340)
+
+
+
diff --git a/Ghidra/Processors/68000/data/languages/68000.sinc b/Ghidra/Processors/68000/data/languages/68000.sinc
index 674c9d06535..b6a8b957215 100644
--- a/Ghidra/Processors/68000/data/languages/68000.sinc
+++ b/Ghidra/Processors/68000/data/languages/68000.sinc
@@ -59,6 +59,10 @@ define register offset=0x746 size=10 [ FP7 ];
@define DAT_DIR_CTL_ADDR_MODES2 "(mode2=0 | mode2=2 | mode2=5 | mode2=6 | mode=7)" # Data direct and control addressing modes
@define CTL_ADDR_MODES2 "(mode2=2 | mode2=5 | mode2=6 | mode2=7)" # Control addressing modes
+@ifdef CPU32
+@define TBL_ADDR_MODES "(tbl_mode=2 | tbl_mode=4 | tbl_mode=5 | tbl_mode=6 | tbl_mode=7)"
+@endif
+
# Floating-point condition code bits within FPSR
@define N_FP "FPSR[27,1]"
@define Z_FP "FPSR[26,1]"
@@ -173,9 +177,29 @@ define token extword (16)
reg12x = (12,15)
reg12xwu = (12,15)
reg12xwl = (12,15)
-@endif
+@endif
+@ifdef CPU32
+ tbl_dr_size = (6,7)
+ tbl_dr_round = (10,10)
+ tbl_dr_sign = (11,11)
+ tbl_dr_reg = (0,2)
+@endif
;
+@ifdef CPU32
+define token tbl_instrA(32)
+ tbl_regan=(16,18)
+ tbl_mode=(19,21)
+ tbl_op37=(19,23)
+ tbl_op67=(22,23)
+ tbl_opbig=(24,31)
+ tbl_size=(6,7)
+ tbl_round=(10,10)
+ tbl_sign=(11,11)
+ tbl_regxdn=(12,14)
+;
+@endif
+
define token extword2 (16)
regda2 = (12,15)
ext2_911 = (9,11)
@@ -307,12 +331,20 @@ define context contextreg
extGUARD = (14,14) # guard for saving off modes before starting instructions
;
+@ifdef CPU32
+attach variables [ regdn regxdn reg9dn regdr regdq regsdn regdu regdc regdu2 regdc2 tbl_regxdn tbl_dr_reg ] [ D0 D1 D2 D3 D4 D5 D6 D7 ];
+@else
attach variables [ regdn regxdn reg9dn regdr regdq regsdn regdu regdc regdu2 regdc2 ] [ D0 D1 D2 D3 D4 D5 D6 D7 ];
+@endif
attach variables [ fldoffreg fldwdreg f_reg fcnt fkfacreg fldynreg ] [ D0 D1 D2 D3 D4 D5 D6 D7 ];
attach variables [ regdnw regxdnw reg9dnw regsdnw regduw regdcw regdu2w regdc2w ] [ D0w D1w D2w D3w D4w D5w D6w D7w ];
attach variables [ regdnb reg9dnb regsdnb regdub regdcb ] [ D0b D1b D2b D3b D4b D5b D6b D7b ];
attach variables [ regda regda2 ] [ D0 D1 D2 D3 D4 D5 D6 D7 A0 A1 A2 A3 A4 A5 A6 SP ];
+@ifdef CPU32
+attach variables [ regan regxan reg9an regfan regsan aregx tbl_regan ] [ A0 A1 A2 A3 A4 A5 A6 SP ];
+@else
attach variables [ regan regxan reg9an regfan regsan aregx ] [ A0 A1 A2 A3 A4 A5 A6 SP ];
+@endif
attach variables [ reganw regxanw regsanw ] [ A0w A1w A2w A3w A4w A5w A6w A7w ];
attach variables [ reganb regsanb ] [ A0b A1b A2b A3b A4b A5b A6b A7b ];
@@ -1051,6 +1083,7 @@ with : extGUARD=1 {
local source = regdn; mask:4 = 1 << (const8 & 31); ZF = (source & mask) == 0; regdn = source & (~mask);
}
+@ifndef CPU32
bfOffWd: {f_off:f_wd} is f_off & f_wd { }
:bfchg e2l^bfOffWd is opbig=0xea & op67=3 & $(DAT_DIR_CTL_ADDR_MODES); bfOffWd & f_off & f_wd; e2l [ savmod2=savmod1; regtsan=regtfan; ] {
logflags();
@@ -1144,6 +1177,7 @@ bfOffWd: {f_off:f_wd} is f_off & f_wd { }
getbitfield(tmp, f_off, f_wd);
resbitflags(tmp, f_wd-1);
}
+@endif # CPU32 - no bitfield instructions
define pcodeop breakpoint;
:bkpt "#"op02 is opbig=0x48 & op67=1 & op5=0 & op34=1 & op02 { breakpoint(); }
@@ -1205,7 +1239,8 @@ define pcodeop breakpoint;
}
@endif # COLDFIRE
-# TODO: Determine layout of a module descriptor
+@ifndef CPU32
+# TODO: Determine layout of a module descriptor
define pcodeop callm;
:callm const8,e2l is opbig=6 & op67=3 & $(CTL_ADDR_MODES); const8; e2l [ savmod2=savmod1; regtsan=regtfan; ] {
PC = callm(const8, e2l);
@@ -1283,6 +1318,7 @@ define pcodeop callm;
ZF = 1;
NF = 0;
}
+@endif # CPU32 - no CALLM, CAS, CAS2
:chk.w eaw,reg9dnw is (op=4 & reg9dnw & op68=6 & $(DAT_ALTER_ADDR_MODES))... & eaw {
build eaw;
@@ -1771,37 +1807,37 @@ r2mfl0: { r2mfl1} is mvm15=0 & r2mfl1 { }
# Register to memory, backward direction, via word
-r2mbwf: A7w is A7w & mvm0=1 { movemptr = movemptr - 4; *movemptr = A7w; }
+r2mbwf: A7w is A7w & mvm0=1 { movemptr = movemptr - 2; *movemptr = A7w; }
r2mbwf: is mvm0=0 { }
-r2mbwe: r2mbwf" "A6w is A6w & mvm1=1 & r2mbwf { movemptr = movemptr - 4; *movemptr = A6w; }
+r2mbwe: r2mbwf" "A6w is A6w & mvm1=1 & r2mbwf { movemptr = movemptr - 2; *movemptr = A6w; }
r2mbwe: r2mbwf is mvm1=0 & r2mbwf { }
-r2mbwd: r2mbwe" "A5w is A5w & mvm2=1 & r2mbwe { movemptr = movemptr - 4; *movemptr = A5w; }
+r2mbwd: r2mbwe" "A5w is A5w & mvm2=1 & r2mbwe { movemptr = movemptr - 2; *movemptr = A5w; }
r2mbwd: r2mbwe is mvm2=0 & r2mbwe { }
-r2mbwc: r2mbwd" "A4w is A4w & mvm3=1 & r2mbwd { movemptr = movemptr - 4; *movemptr = A4w; }
+r2mbwc: r2mbwd" "A4w is A4w & mvm3=1 & r2mbwd { movemptr = movemptr - 2; *movemptr = A4w; }
r2mbwc: r2mbwd is mvm3=0 & r2mbwd { }
-r2mbwb: r2mbwc" "A3w is A3w & mvm4=1 & r2mbwc { movemptr = movemptr - 4; *movemptr = A3w; }
+r2mbwb: r2mbwc" "A3w is A3w & mvm4=1 & r2mbwc { movemptr = movemptr - 2; *movemptr = A3w; }
r2mbwb: r2mbwc is mvm4=0 & r2mbwc { }
-r2mbwa: r2mbwb" "A2w is A2w & mvm5=1 & r2mbwb { movemptr = movemptr - 4; *movemptr = A2w; }
+r2mbwa: r2mbwb" "A2w is A2w & mvm5=1 & r2mbwb { movemptr = movemptr - 2; *movemptr = A2w; }
r2mbwa: r2mbwb is mvm5=0 & r2mbwb { }
-r2mbw9: r2mbwa" "A1w is A1w & mvm6=1 & r2mbwa { movemptr = movemptr - 4; *movemptr = A1w; }
+r2mbw9: r2mbwa" "A1w is A1w & mvm6=1 & r2mbwa { movemptr = movemptr - 2; *movemptr = A1w; }
r2mbw9: r2mbwa is mvm6=0 & r2mbwa { }
-r2mbw8: r2mbw9" "A0w is A0w & mvm7=1 & r2mbw9 { movemptr = movemptr - 4; *movemptr = A0w; }
+r2mbw8: r2mbw9" "A0w is A0w & mvm7=1 & r2mbw9 { movemptr = movemptr - 2; *movemptr = A0w; }
r2mbw8: r2mbw9 is mvm7=0 & r2mbw9 { }
-r2mbw7: r2mbw8" "D7w is D7w & mvm8=1 & r2mbw8 { movemptr = movemptr - 4; *movemptr = D7w; }
+r2mbw7: r2mbw8" "D7w is D7w & mvm8=1 & r2mbw8 { movemptr = movemptr - 2; *movemptr = D7w; }
r2mbw7: r2mbw8 is mvm8=0 & r2mbw8 { }
-r2mbw6: r2mbw7" "D6w is D6w & mvm9=1 & r2mbw7 { movemptr = movemptr - 4; *movemptr = D6w; }
+r2mbw6: r2mbw7" "D6w is D6w & mvm9=1 & r2mbw7 { movemptr = movemptr - 2; *movemptr = D6w; }
r2mbw6: r2mbw7 is mvm9=0 & r2mbw7 { }
-r2mbw5: r2mbw6" "D5w is D5w & mvm10=1 & r2mbw6 { movemptr = movemptr - 4; *movemptr = D5w; }
+r2mbw5: r2mbw6" "D5w is D5w & mvm10=1 & r2mbw6 { movemptr = movemptr - 2; *movemptr = D5w; }
r2mbw5: r2mbw6 is mvm10=0 & r2mbw6 { }
-r2mbw4: r2mbw5" "D4w is D4w & mvm11=1 & r2mbw5 { movemptr = movemptr - 4; *movemptr = D4w; }
+r2mbw4: r2mbw5" "D4w is D4w & mvm11=1 & r2mbw5 { movemptr = movemptr - 2; *movemptr = D4w; }
r2mbw4: r2mbw5 is mvm11=0 & r2mbw5 { }
-r2mbw3: r2mbw4" "D3w is D3w & mvm12=1 & r2mbw4 { movemptr = movemptr - 4; *movemptr = D3w; }
+r2mbw3: r2mbw4" "D3w is D3w & mvm12=1 & r2mbw4 { movemptr = movemptr - 2; *movemptr = D3w; }
r2mbw3: r2mbw4 is mvm12=0 & r2mbw4 { }
-r2mbw2: r2mbw3" "D2w is D2w & mvm13=1 & r2mbw3 { movemptr = movemptr - 4; *movemptr = D2w; }
+r2mbw2: r2mbw3" "D2w is D2w & mvm13=1 & r2mbw3 { movemptr = movemptr - 2; *movemptr = D2w; }
r2mbw2: r2mbw3 is mvm13=0 & r2mbw3 { }
-r2mbw1: r2mbw2" "D1w is D1w & mvm14=1 & r2mbw2 { movemptr = movemptr - 4; *movemptr = D1w; }
+r2mbw1: r2mbw2" "D1w is D1w & mvm14=1 & r2mbw2 { movemptr = movemptr - 2; *movemptr = D1w; }
r2mbw1: r2mbw2 is mvm14=0 & r2mbw2 { }
-r2mbw0: { r2mbw1" "D0w} is D0w & mvm15=1 & r2mbw1 { movemptr = movemptr - 4; *movemptr = D0w; }
+r2mbw0: { r2mbw1" "D0w} is D0w & mvm15=1 & r2mbw1 { movemptr = movemptr - 2; *movemptr = D0w; }
r2mbw0: { r2mbw1} is mvm15=0 & r2mbw1 { }
@@ -2060,6 +2096,7 @@ macro negResFlags(result) {
:ori const8,"CCR" is opbig=0 & op37=7 & op02=4; const8 { packflags(SR); SR=SR|zext(const8); unpackflags(SR); }
:ori const16,SR is SR; opbig=0x00 & d8base=0x7c; const16 { packflags(SR); SR=SR|const16; unpackflags(SR); }
+@ifndef CPU32
:pack Tyw,Txw,const16 is op=8 & op48=20 & Txw & Tyw & rmbit=0; const16 {
local value = (Tyw & 0x0F0F) + const16;
Txw = (Txw & 0xFF00) | ((value & 0x0F00) >> 4) | (value & 0x000F);
@@ -2070,6 +2107,7 @@ macro negResFlags(result) {
local result:2 = ((value & 0x0F00) >> 4) | (value & 0x000F);
Txb = result:1;
}
+@endif # CPU32 - no PACK
:pea eaptr is (opbig=0x48 & op67=1 & $(CTL_ADDR_MODES))... & eaptr { value:4 = eaptr; SP = SP-4; *SP = value; }
@@ -2227,9 +2265,11 @@ ptestLevel: "#"^mregn is mregn { export *[const]:1 mregn; }
:rtd const16 is opbig=0x4e & op37=14 & op02=4; const16 { PC = *SP; SP = SP + 4 + zext(const16); return [PC]; }
:rte is d16=0x4e73 { tmp:4 = 0; return [tmp]; }
+@ifndef CPU32
define pcodeop rtm;
:rtm regdn is opbig=0x06 & op37=24 & regdn { PC = rtm(regdn); return [PC]; }
:rtm regan is opbig=0x06 & op37=25 & regan { PC = rtm(regan); return [PC];}
+@endif # CPU32 - no RTM
:rtr is opbig=0x4e & op37=14 & op02=7 { SR = *SP; SP = SP+2; PC = *SP; SP = SP+4; unpackflags(SR); return [PC]; }
@@ -2306,6 +2346,7 @@ define pcodeop stop;
:unlk regan is opbig=0x4e & op37=11 & regan { SP = regan; regan = *SP; SP = SP+4; }
+@ifndef CPU32
:unpk Tyw,Txw,const16 is op=8 & Txw & op48=24 & Tyw & rmbit=0; const16 {
Txw = (Txw & 0xF0F0) | ((((Tyw & 0x00F0) << 4) | (Tyw & 0x000F)) + const16);
}
@@ -2315,7 +2356,9 @@ define pcodeop stop;
source = (((source & 0x00F0) << 4) | (source & 0x000F)) + const16;
Txw = (Txw & 0xF0F0) | source;
}
+@endif # CPU32 - no UNPK
+@ifndef CPU32
# Floating Point Instructions
# 68040 directly implements Floating Point instructions but requires Coprocessor ID be 001
@@ -2883,6 +2926,7 @@ fsubrnd: "d" is fdst & fopmode=0x6c { tmp:4 = float2float(fdst); fdst = float2f
:ftwotox.x fsrc, fdst is op=15 & $(FP_COP) & op68=0 & mode=0 & regan=0; frm=0 & f1515=0 & f1313=0 & fsrc & fdst & fopmode=0x11
{ fdst = ftwotox(fsrc); }
+@endif # CPU32 - no FPU instructions
@ifdef COLDFIRE
@@ -3151,4 +3195,97 @@ moveaccreg2: ACC3 is ACC3 & op01=3 { export ACC3; }
@endif
+@ifdef CPU32
+
+tbl_eal: (tbl_regan) is tbl_mode=2 & tbl_regan { export *:4 tbl_regan; }
+tbl_eal: -(tbl_regan) is tbl_mode=4 & tbl_regan { tbl_regan = tbl_regan - 4; export *:4 tbl_regan; }
+tbl_eal: (d16,tbl_regan) is tbl_mode=5 & tbl_regan; d16 { local tmp = tbl_regan + d16; export *:4 tmp; }
+tbl_eal: (extw) is tbl_mode=6 & tbl_regan; extw [ regtfan = tbl_regan; pcmode = 0; ] { build extw; export *:4 extw; }
+tbl_eal: (d16,PC) is PC & tbl_mode=7 & tbl_regan=2; d16 { tmp:4 = inst_start + 2 + d16; export *:4 tmp; }
+tbl_eal: (extw) is tbl_mode=7 & tbl_regan=3; extw [ pcmode=1; ] { build extw; export *:4 extw; }
+tbl_eal: (d16)".w" is tbl_mode=7 & tbl_regan=0; d16 { export *:4 d16; }
+tbl_eal: (d32)".l" is tbl_mode=7 & tbl_regan=1; d32 { export *:4 d32; }
+tbl_eal: "#"^d32 is tbl_mode=7 & tbl_regan=4; d32 { export *[const]:4 d32; }
+
+tbl_eaw: (tbl_regan) is tbl_mode=2 & tbl_regan { export *:2 tbl_regan; }
+tbl_eaw: -(tbl_regan) is tbl_mode=4 & tbl_regan { tbl_regan = tbl_regan - 2; export *:2 tbl_regan; }
+tbl_eaw: (d16,tbl_regan) is tbl_mode=5 & tbl_regan; d16 { local tmp = tbl_regan + d16; export *:2 tmp; }
+tbl_eaw: (extw) is tbl_mode=6 & tbl_regan; extw [ pcmode=0; regtfan=tbl_regan; ] { build extw; export *:2 extw; }
+tbl_eaw: (d16,PC) is PC & tbl_mode=7 & tbl_regan=2; d16 { tmp:4 = inst_start + 2 + d16; export *:2 tmp; }
+tbl_eaw: (extw) is tbl_mode=7 & tbl_regan=3; extw [ pcmode=1; ] { build extw; export *:2 extw; }
+tbl_eaw: (d16)".w" is tbl_mode=7 & tbl_regan=0; d16 { export *:2 d16; }
+tbl_eaw: (d32)".l" is tbl_mode=7 & tbl_regan=1; d32 { export *:2 d32; }
+tbl_eaw: "#"^d16 is tbl_mode=7 & tbl_regan=4; d16 { export *[const]:2 d16; }
+
+tbl_eab: (tbl_regan) is tbl_mode=2 & tbl_regan { export *:1 tbl_regan; }
+tbl_eab: -(tbl_regan) is tbl_mode=4 & tbl_regan { tbl_regan = tbl_regan - 1; export *:1 tbl_regan; }
+tbl_eab: (d16,tbl_regan) is tbl_mode=5 & tbl_regan; d16 { local tmp = tbl_regan + d16; export *:1 tmp; }
+tbl_eab: (extw) is tbl_mode=6 & tbl_regan; extw [ pcmode=0; regtfan=tbl_regan; ] { build extw; export *:1 extw; }
+tbl_eab: (d16,PC) is PC & tbl_mode=7 & tbl_regan=2; d16 { tmp:4 = inst_start + 2 + d16; export *:1 tmp; }
+tbl_eab: (extw) is tbl_mode=7 & tbl_regan=3; extw [ pcmode=1; ] { build extw; export *:1 extw; }
+tbl_eab: (d16)".w" is tbl_mode=7 & tbl_regan=0; d16 { export *:1 d16; }
+tbl_eab: (d32)".l" is tbl_mode=7 & tbl_regan=1; d32 { export *:1 d32; }
+tbl_eab: "#"^d8 is tbl_mode=7 & tbl_regan=4; d8 { export *[const]:1 d8; }
+
+tblsign: "u" is tbl_sign=0 { }
+tblsign: "s" is tbl_sign=1 { }
+
+tbldrsign: "u" is tbl_dr_sign=0 { }
+tbldrsign: "s" is tbl_dr_sign=1 { }
+
+define pcodeop tableLookup;
+
+# Rounded Table Lookup and Interpolate
+
+:tbl^tblsign^".b" tbl_eab,tbl_regxdn is (tbl_opbig=0xF8 & tbl_op67=0 & $(TBL_ADDR_MODES) & tbl_size=0 & tblsign & tbl_round=0 & tbl_regxdn) ... & tbl_eab
+ { tbl_regxdn = tableLookup(tbl_regxdn, tbl_eab); }
+
+:tbl^tblsign^".w" tbl_eaw,tbl_regxdn is (tbl_opbig=0xF8 & tbl_op67=0 & $(TBL_ADDR_MODES) & tbl_size=1 & tblsign & tbl_round=0 & tbl_regxdn) ... & tbl_eaw
+ { tbl_regxdn = tableLookup(tbl_regxdn, tbl_eaw); }
+
+:tbl^tblsign^".l" tbl_eal,tbl_regxdn is (tbl_opbig=0xF8 & tbl_op67=0 & $(TBL_ADDR_MODES) & tbl_size=2 & tblsign & tbl_round=0 & tbl_regxdn) ... & tbl_eal
+ { tbl_regxdn = tableLookup(tbl_regxdn, tbl_eal); }
+
+# Unrounded Table Lookup and Interpolate
+
+:tbl^tblsign^"n.b" tbl_eab,tbl_regxdn is (tbl_opbig=0xF8 & tbl_op67=0 & $(TBL_ADDR_MODES) & tbl_size=0 & tblsign & tbl_round=1 & tbl_regxdn) ... & tbl_eab
+ { tbl_regxdn = tableLookup(tbl_regxdn, tbl_eab); }
+
+:tbl^tblsign^"n.w" tbl_eaw,tbl_regxdn is (tbl_opbig=0xF8 & tbl_op67=0 & $(TBL_ADDR_MODES) & tbl_size=1 & tblsign & tbl_round=1 & tbl_regxdn) ... & tbl_eaw
+ { tbl_regxdn = tableLookup(tbl_regxdn, tbl_eaw); }
+
+:tbl^tblsign^"n.l" tbl_eal,tbl_regxdn is (tbl_opbig=0xF8 & tbl_op67=0 & $(TBL_ADDR_MODES) & tbl_size=2 & tblsign & tbl_round=1 & tbl_regxdn) ... & tbl_eal
+ { tbl_regxdn = tableLookup(tbl_regxdn, tbl_eal); }
+
+define pcodeop interpolate;
+
+# Rounded Data Register Interpolate
+
+:tbl^tbldrsign^".b" regdn:tbl_dr_reg,regxdn is opbig=0xF8 & op37=0 & mode=0 & regdn ; tbl_dr_size=0 & tbldrsign & tbl_dr_round=0 & tbl_dr_reg & regxdn
+ { regxdn = interpolate(regdn, tbl_dr_reg); }
+
+:tbl^tbldrsign^".w" regdn:tbl_dr_reg,regxdn is opbig=0xF8 & op37=0 & mode=0 & regdn ; tbl_dr_size=1 & tbldrsign & tbl_dr_round=0 & tbl_dr_reg & regxdn
+ { regxdn = interpolate(regdn, tbl_dr_reg); }
+
+:tbl^tbldrsign^".l" regdn:tbl_dr_reg,regxdn is opbig=0xF8 & op37=0 & mode=0 & regdn ; tbl_dr_size=2 & tbldrsign & tbl_dr_round=0 & tbl_dr_reg & regxdn
+ { regxdn = interpolate(regdn, tbl_dr_reg); }
+
+# Unrounded Data Register Interpolate
+
+:tbl^tbldrsign^"n.b" regdn:tbl_dr_reg,regxdn is opbig=0xF8 & op37=0 & mode=0 & regdn ; tbl_dr_size=0 & tbldrsign & tbl_dr_round=1 & tbl_dr_reg & regxdn
+ { regxdn = interpolate(regdn, tbl_dr_reg); }
+
+:tbl^tbldrsign^"n.w" regdn:tbl_dr_reg,regxdn is opbig=0xF8 & op37=0 & mode=0 & regdn ; tbl_dr_size=1 & tbldrsign & tbl_dr_round=1 & tbl_dr_reg & regxdn
+ { regxdn = interpolate(regdn, tbl_dr_reg); }
+
+:tbl^tbldrsign^"n.l" regdn:tbl_dr_reg,regxdn is opbig=0xF8 & op37=0 & mode=0 & regdn ; tbl_dr_size=2 & tbldrsign & tbl_dr_round=1 & tbl_dr_reg & regxdn
+ { regxdn = interpolate(regdn, tbl_dr_reg); }
+
+define pcodeop low_power_stop;
+:lpstop "#"^d16 is opbig=0xf8 & d8base=0x00; opbig=0x01 & d8base=0xC0; d16 { SR = d16; low_power_stop(); }
+
+define pcodeop background_mode;
+:bgnd is opbig=0x4A & d8base=0xFA { background_mode(); }
+
+@endif # CPU32
} # end with : extGUARD=1
diff --git a/Ghidra/Processors/68000/data/languages/CPU32.slaspec b/Ghidra/Processors/68000/data/languages/CPU32.slaspec
new file mode 100644
index 00000000000..dc2c54ab6f1
--- /dev/null
+++ b/Ghidra/Processors/68000/data/languages/CPU32.slaspec
@@ -0,0 +1,6 @@
+# Motorola's CPU32 processor
+
+@define CPU32 ""
+@define MC68332 ""
+
+@include "68000.sinc"
diff --git a/Ghidra/Processors/68000/src/test.processors/java/ghidra/test/processors/CPU32_O0_EmulatorTest.java b/Ghidra/Processors/68000/src/test.processors/java/ghidra/test/processors/CPU32_O0_EmulatorTest.java
new file mode 100644
index 00000000000..583c7052a99
--- /dev/null
+++ b/Ghidra/Processors/68000/src/test.processors/java/ghidra/test/processors/CPU32_O0_EmulatorTest.java
@@ -0,0 +1,40 @@
+/* ###
+ * 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.test.processors;
+
+import ghidra.test.processors.support.ProcessorEmulatorTestAdapter;
+import junit.framework.Test;
+
+public class CPU32_O0_EmulatorTest extends ProcessorEmulatorTestAdapter {
+
+ private static final String LANGUAGE_ID = "68000:BE:32:CPU32";
+ private static final String COMPILER_SPEC_ID = "default";
+
+ private static final String[] REG_DUMP_SET = new String[] {};
+
+ public CPU32_O0_EmulatorTest(String name) throws Exception {
+ super(name, LANGUAGE_ID, COMPILER_SPEC_ID, REG_DUMP_SET);
+ }
+
+ @Override
+ protected String getProcessorDesignator() {
+ return "CPU32_GCC_O0";
+ }
+
+ public static Test suite() {
+ return ProcessorEmulatorTestAdapter.buildEmulatorTestSuite(CPU32_O0_EmulatorTest.class);
+ }
+}
diff --git a/Ghidra/Processors/68000/src/test.processors/java/ghidra/test/processors/CPU32_O3_EmulatorTest.java b/Ghidra/Processors/68000/src/test.processors/java/ghidra/test/processors/CPU32_O3_EmulatorTest.java
new file mode 100644
index 00000000000..086c4ab070c
--- /dev/null
+++ b/Ghidra/Processors/68000/src/test.processors/java/ghidra/test/processors/CPU32_O3_EmulatorTest.java
@@ -0,0 +1,40 @@
+/* ###
+ * 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.test.processors;
+
+import ghidra.test.processors.support.ProcessorEmulatorTestAdapter;
+import junit.framework.Test;
+
+public class CPU32_O3_EmulatorTest extends ProcessorEmulatorTestAdapter {
+
+ private static final String LANGUAGE_ID = "68000:BE:32:CPU32";
+ private static final String COMPILER_SPEC_ID = "default";
+
+ private static final String[] REG_DUMP_SET = new String[] {};
+
+ public CPU32_O3_EmulatorTest(String name) throws Exception {
+ super(name, LANGUAGE_ID, COMPILER_SPEC_ID, REG_DUMP_SET);
+ }
+
+ @Override
+ protected String getProcessorDesignator() {
+ return "CPU32_GCC_O3";
+ }
+
+ public static Test suite() {
+ return ProcessorEmulatorTestAdapter.buildEmulatorTestSuite(CPU32_O3_EmulatorTest.class);
+ }
+}
diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc b/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc
index 2811de5d481..a3d57128eda 100644
--- a/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc
+++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64base.sinc
@@ -5659,7 +5659,7 @@ is size.ldstr=2 & b_2429=0x8 & b_23=1 & L=0 & b_21=0 & b_15=1 & addrReg & Rt_GPR
:stlrb Rt_GPR32, addrReg
is size.ldstr=0 & b_2429=0x8 & b_23=1 & L=0 & b_21=0 & b_15=1 & addrReg & Rt_GPR32
{
- *addrReg = Rt_GPR32;
+ *:1 addrReg = Rt_GPR32:1;
}
# C6.2.312 STLRH page C6-1849 line 108967 MATCH x48808000/mask=xffe08000
@@ -5670,7 +5670,7 @@ is size.ldstr=0 & b_2429=0x8 & b_23=1 & L=0 & b_21=0 & b_15=1 & addrReg & Rt_GPR
:stlrh Rt_GPR32, addrReg
is size.ldstr=1 & b_2429=0x8 & b_23=1 & L=0 & b_21=0 & b_15=1 & addrReg & Rt_GPR32
{
- *addrReg = Rt_GPR32;
+ *:2 addrReg = Rt_GPR32:2;
}
# C6.2.313 STLUR page C6-1850 line 109030 MATCH x99000000/mask=xbfe00c00
diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc b/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc
index 4ff3386c3cf..9da59d17fa3 100644
--- a/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc
+++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64neon.sinc
@@ -734,7 +734,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
Rd_FPR8 = Rd_FPR8 + Rn_VPR128.16B[104,8];
Rd_FPR8 = Rd_FPR8 + Rn_VPR128.16B[112,8];
Rd_FPR8 = Rd_FPR8 + Rn_VPR128.16B[120,8];
- zext_zq(Zd); # zero upper 31 bytes of Zd
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.6 ADDV page C7-2027 line 118452 MATCH x0e31b800/mask=xbf3ffc00
@@ -753,7 +753,7 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
Rd_FPR8 = Rd_FPR8 + Rn_VPR64.8B[40,8];
Rd_FPR8 = Rd_FPR8 + Rn_VPR64.8B[48,8];
Rd_FPR8 = Rd_FPR8 + Rn_VPR64.8B[56,8];
- zext_zq(Zd); # zero upper 31 bytes of Zd
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.6 ADDV page C7-2027 line 118452 MATCH x0e31b800/mask=xbf3ffc00
@@ -7275,6 +7275,7 @@ is b_1031=0b0001111001111110000000 & Rd_GPR32 & Rn_FPR64 & Rd_GPR64
is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=1 & b_21=0 & Rm_FPR64 & b_15=0 & Ra_FPR64 & Rn_FPR64 & Rd_FPR64 & Zd
{
Rd_FPR64 = Ra_FPR64 f+ (Rm_FPR64 f* Rn_FPR64); #NEON_fmadd(Rn_FPR64, Rm_FPR64, Ra_FPR64);
+ zext_zd(Zd); # zero upper bytes of Zd
}
# C7.2.100 FMADD page C7-2246 line 131323 MATCH x1f000000/mask=xff208000
@@ -7286,6 +7287,7 @@ is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=1 & b_21=0 & Rm_FPR64 & b_15=0 & R
is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=0 & b_21=0 & Rm_FPR32 & b_15=0 & Ra_FPR32 & Rn_FPR32 & Rd_FPR32 & Zd
{
Rd_FPR32 = Ra_FPR32 f+ (Rm_FPR32 f* Rn_FPR32); #NEON_fmadd(Rn_FPR32, Rm_FPR32, Ra_FPR32);
+ zext_zs(Zd); # zero upper bytes of Zd
}
# C7.2.100 FMADD page C7-2246 line 131323 MATCH x1f000000/mask=xff208000
@@ -7297,6 +7299,7 @@ is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=0 & b_21=0 & Rm_FPR32 & b_15=0 & R
is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=3 & b_21=0 & Rm_FPR16 & b_15=0 & Ra_FPR16 & Rn_FPR16 & Rd_FPR16 & Zd
{
Rd_FPR16 = Ra_FPR16 f+ (Rm_FPR16 f* Rn_FPR16); #NEON_fmadd(Rn_FPR16, Rm_FPR16, Ra_FPR16);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.101 FMAX (vector) page C7-2248 line 131451 MATCH x0e20f400/mask=xbfa0fc00
@@ -9562,6 +9565,7 @@ is m=0 & b_3030=0 & s=0 & b_2428=0x1e & ftype=3 & b_2121=1 & Imm8_fmov16_operand
is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=1 & b_21=0 & Rm_FPR64 & b_15=1 & Ra_FPR64 & Rn_FPR64 & Rd_FPR64 & Zd
{
Rd_FPR64 = Ra_FPR64 f- (Rm_FPR64 f* Rn_FPR64);
+ zext_zd(Zd); # zero upper bytes of Zd
}
@@ -9574,6 +9578,7 @@ is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=1 & b_21=0 & Rm_FPR64 & b_15=1 & R
is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=0 & b_21=0 & Rm_FPR32 & b_15=1 & Ra_FPR32 & Rn_FPR32 & Rd_FPR32 & Zd
{
Rd_FPR32 = Ra_FPR32 f- (Rm_FPR32 f* Rn_FPR32);
+ zext_zs(Zd); # zero upper bytes of Zd
}
# C7.2.133 FMSUB page C7-2318 line 135582 MATCH x1f008000/mask=xff208000
@@ -9585,6 +9590,7 @@ is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=0 & b_21=0 & Rm_FPR32 & b_15=1 & R
is m=0 & b_3030=0 & s=0 & b_2428=0x1f & ftype=3 & b_21=0 & Rm_FPR16 & b_15=1 & Ra_FPR16 & Rn_FPR16 & Rd_FPR16 & Zd
{
Rd_FPR16 = Ra_FPR16 f- (Rm_FPR16 f* Rn_FPR16);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.134 FMUL (by element) page C7-2320 line 135711 MATCH x5f009000/mask=xffc0f400
@@ -17093,6 +17099,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR128.8H
is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR128.16B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_smaxv(Rn_VPR128.16B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.270 SMAXV page C7-2616 line 152715 MATCH x0e30a800/mask=xbf3ffc00
@@ -17104,6 +17111,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR64.8B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_smaxv(Rn_VPR64.8B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.270 SMAXV page C7-2616 line 152715 MATCH x0e30a800/mask=xbf3ffc00
@@ -17115,6 +17123,7 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR64.4H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_smaxv(Rn_VPR64.4H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.270 SMAXV page C7-2616 line 152715 MATCH x0e30a800/mask=xbf3ffc00
@@ -17126,6 +17135,7 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR128.8H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_smaxv(Rn_VPR128.8H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.270 SMAXV page C7-2616 line 152715 MATCH x0e30a800/mask=xbf3ffc00
@@ -17137,6 +17147,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR128.4S & Rd_FPR32 & Zd
{
Rd_FPR32 = NEON_smaxv(Rn_VPR128.4S, 4:1);
+ zext_zs(Zd); # zero upper bytes of Zd
}
# C7.2.271 SMIN page C7-2618 line 152818 MATCH x0e206c00/mask=xbf20fc00
@@ -17280,6 +17291,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR128.8H
is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR128.16B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_sminv(Rn_VPR128.16B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.273 SMINV page C7-2622 line 153024 MATCH x0e31a800/mask=xbf3ffc00
@@ -17291,6 +17303,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR64.8B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_sminv(Rn_VPR64.8B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.273 SMINV page C7-2622 line 153024 MATCH x0e31a800/mask=xbf3ffc00
@@ -17302,6 +17315,7 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR64.4H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_sminv(Rn_VPR64.4H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.273 SMINV page C7-2622 line 153024 MATCH x0e31a800/mask=xbf3ffc00
@@ -17313,6 +17327,7 @@ is b_3131=0 & q=0 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR128.8H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_sminv(Rn_VPR128.8H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.273 SMINV page C7-2622 line 153024 MATCH x0e31a800/mask=xbf3ffc00
@@ -17324,6 +17339,7 @@ is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=0 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR128.4S & Rd_FPR32 & Zd
{
Rd_FPR32 = NEON_sminv(Rn_VPR128.4S, 4:1);
+ zext_zs(Zd); # zero upper bytes of Zd
}
# C7.2.274 SMLAL, SMLAL2 (by element) page C7-2624 line 153127 MATCH x0f002000/mask=xbf00f400
@@ -26673,6 +26689,7 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR128.8H
is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR128.16B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_umaxv(Rn_VPR128.16B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.362 UMAXV page C7-2849 line 166369 MATCH x2e30a800/mask=xbf3ffc00
@@ -26684,6 +26701,7 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR64.8B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_umaxv(Rn_VPR64.8B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.362 UMAXV page C7-2849 line 166369 MATCH x2e30a800/mask=xbf3ffc00
@@ -26695,6 +26713,7 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR64.4H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_umaxv(Rn_VPR64.4H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.362 UMAXV page C7-2849 line 166369 MATCH x2e30a800/mask=xbf3ffc00
@@ -26706,6 +26725,7 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR128.8H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_umaxv(Rn_VPR128.8H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.362 UMAXV page C7-2849 line 166369 MATCH x2e30a800/mask=xbf3ffc00
@@ -26717,6 +26737,7 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x18 & b_1216=0xa & b_1011=2 & Rn_VPR128.4S & Rd_FPR32 & Zd
{
Rd_FPR32 = NEON_umaxv(Rn_VPR128.4S, 4:1);
+ zext_zs(Zd); # zero upper bytes of Zd
}
# C7.2.363 UMIN page C7-2851 line 166472 MATCH x2e206c00/mask=xbf20fc00
@@ -26860,6 +26881,7 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_2121=1 & Rm_VPR128.8H
is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR128.16B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_uminv(Rn_VPR128.16B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.365 UMINV page C7-2855 line 166678 MATCH x2e31a800/mask=xbf3ffc00
@@ -26871,6 +26893,7 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR64.8B & Rd_FPR8 & Zd
{
Rd_FPR8 = NEON_uminv(Rn_VPR64.8B, 1:1);
+ zext_zb(Zd); # zero upper bytes of Zd
}
# C7.2.365 UMINV page C7-2855 line 166678 MATCH x2e31a800/mask=xbf3ffc00
@@ -26882,6 +26905,7 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=0 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR64.4H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_uminv(Rn_VPR64.4H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.365 UMINV page C7-2855 line 166678 MATCH x2e31a800/mask=xbf3ffc00
@@ -26893,6 +26917,7 @@ is b_3131=0 & q=0 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR128.8H & Rd_FPR16 & Zd
{
Rd_FPR16 = NEON_uminv(Rn_VPR128.8H, 2:1);
+ zext_zh(Zd); # zero upper bytes of Zd
}
# C7.2.365 UMINV page C7-2855 line 166678 MATCH x2e31a800/mask=xbf3ffc00
@@ -26904,6 +26929,7 @@ is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=1 & b_1721=0x18 & b_1216=0x
is b_3131=0 & q=1 & u=1 & b_2428=0xe & advSIMD3.size=2 & b_1721=0x18 & b_1216=0x1a & b_1011=2 & Rn_VPR128.4S & Rd_FPR32 & Zd
{
Rd_FPR32 = NEON_uminv(Rn_VPR128.4S, 4:1);
+ zext_zs(Zd); # zero upper bytes of Zd
}
# C7.2.366 UMLAL, UMLAL2 (by element) page C7-2857 line 166781 MATCH x2f002000/mask=xbf00f400
diff --git a/Ghidra/Processors/AARCH64/src/main/java/ghidra/app/util/bin/formatt/coff/relocation/AARCH64_CoffRelocationHandler.java b/Ghidra/Processors/AARCH64/src/main/java/ghidra/app/util/bin/formatt/coff/relocation/AARCH64_CoffRelocationHandler.java
index 99974f48e08..eee6c08f6a9 100644
--- a/Ghidra/Processors/AARCH64/src/main/java/ghidra/app/util/bin/formatt/coff/relocation/AARCH64_CoffRelocationHandler.java
+++ b/Ghidra/Processors/AARCH64/src/main/java/ghidra/app/util/bin/formatt/coff/relocation/AARCH64_CoffRelocationHandler.java
@@ -187,6 +187,11 @@ public RelocationResult relocate(Address address, CoffRelocation relocation,
mem.setInt(address, bytesToAdjust);
break;
+ case IMAGE_REL_ARM64_ADDR64:
+ mem.setLong(address, symbolAddr.getOffset());
+ byteLength = 8;
+ break;
+
default: {
return RelocationResult.UNSUPPORTED;
}
diff --git a/Ghidra/Processors/ARM/data/languages/ARMTHUMBinstructions.sinc b/Ghidra/Processors/ARM/data/languages/ARMTHUMBinstructions.sinc
index 1f6b9f8c49f..26a34b46306 100644
--- a/Ghidra/Processors/ARM/data/languages/ARMTHUMBinstructions.sinc
+++ b/Ghidra/Processors/ARM/data/languages/ARMTHUMBinstructions.sinc
@@ -2775,7 +2775,7 @@ mrsipsr: "i" is thc0000=1 & Rd0811 {
b:1 = isCurrentModePrivileged();
if (!b) goto ;
ipsr:4 = getCurrentExceptionNumber();
- Rd0811 = Rd0811 | (ipsr & 0x1f);
+ Rd0811 = Rd0811 | (ipsr & 0x1ff);
}
mrsipsr: is thc0000=0 { }
@@ -2931,12 +2931,12 @@ define pcodeop getProcessStackPointerLimit;
@if defined(VERSION_7M)
-msripsr: "i" is thc0000=1 { }
-msripsr: is thc0000=0 { }
-msrepsr: "e" is thc0101=1 { }
-msrepsr: is thc0101=0 { }
-msrapsr: is thc0202=1 { }
-msrapsr: "a" is thc0202=0 & Rn0003 {
+msripsr: "i" is Rn0003; thc0000=1 { }
+msripsr: is Rn0003; thc0000=0 { }
+msrepsr: "e" is Rn0003; thc0101=1 { }
+msrepsr: is Rn0003; thc0101=0 { }
+msrapsr: is Rn0003; thc0202=1 { }
+msrapsr: "a" is Rn0003; thc0202=0 {
cpsr = cpsr | (Rn0003 & 0xf8000000);
writeAPSR_nzcvq(cpsr);
}
@@ -2944,11 +2944,11 @@ msrapsr: "a" is thc0202=0 & Rn0003 {
msrpsr: msripsr^msrepsr^msrapsr^"psr" is msripsr & msrepsr & msrapsr {
build msrapsr;
}
-msrpsr: "xpsr" is sysm02=3 & msrapsr {
+msrpsr: "xpsr" is (Rn0003 ; sysm02=3) & msrapsr {
build msrapsr;
}
-:msr^ItCond msrpsr,Rn0003 is TMode=1 & ItCond & op4=0xf38 & Rn0003; op12=0x8 & th_psrmask=8 & sysm37=0 & msrpsr
+:msr^ItCond msrpsr,Rn0003 is TMode=1 & ItCond & (op4=0xf38 & Rn0003; op12=0x8 & th_psrmask=8 & sysm37=0) & msrpsr
{
build ItCond;
build msrpsr;
@@ -4358,7 +4358,7 @@ thumbEndianNess: "BE" is op0=0xb658 { export 1:1; }
build ItCond;
}
-:sev^ItCond^".w" is TMode=1 & ItCond & op0=0xf3af; op0=8004
+:sev^ItCond^".w" is TMode=1 & ItCond & op0=0xf3af; op0=0x8004
{
build ItCond;
}
diff --git a/Ghidra/Processors/HCS12/data/languages/HCS_HC12.sinc b/Ghidra/Processors/HCS12/data/languages/HCS_HC12.sinc
index 26ff2c0eaa6..646d9d4eca4 100644
--- a/Ghidra/Processors/HCS12/data/languages/HCS_HC12.sinc
+++ b/Ghidra/Processors/HCS12/data/languages/HCS_HC12.sinc
@@ -271,7 +271,7 @@ macro setHCSphysPage(addr) {
(zext(isPpage) * (0x400000 | ((zext(PPAGE) << 14 ) ^ 0x8000))) +
(zext(isPpage_FF) * (0x400000 | ((0x3FC000) ^ 0xC000))) ;
}
-@elif defined(HCS12) && SIZE=="3"
+@elif defined(HCS12) && SIZE=="3"
macro setHCSphysPage(addr) {
local a3:3 = zext(addr);
@@ -1961,11 +1961,9 @@ with : XGATE=0 {
}
# branch never is a two-byte nop
-SkipNextInstr: dest is epsilon [ dest = inst_next + 1; ] { export *[RAM]:1 dest; }
-:BRN SkipNextInstr is Prefix18=0 & op8=0x21 & SkipNextInstr
+:BRN rel8 is Prefix18=0 & op8=0x21; rel8
{
- goto SkipNextInstr;
}
:BRSET opr8a_8, msk8, rel8 is Prefix18=0 & op8=0x4E; opr8a_8; msk8; rel8
diff --git a/Ghidra/Processors/HCS12/src/main/java/ghidra/app/plugin/core/analysis/HCS12ConventionAnalyzer.java b/Ghidra/Processors/HCS12/src/main/java/ghidra/app/plugin/core/analysis/HCS12ConventionAnalyzer.java
index 5ef162ae20c..5e04ff9c4ae 100644
--- a/Ghidra/Processors/HCS12/src/main/java/ghidra/app/plugin/core/analysis/HCS12ConventionAnalyzer.java
+++ b/Ghidra/Processors/HCS12/src/main/java/ghidra/app/plugin/core/analysis/HCS12ConventionAnalyzer.java
@@ -17,19 +17,11 @@
import java.math.BigInteger;
-import ghidra.app.services.AbstractAnalyzer;
-import ghidra.app.services.AnalysisPriority;
-import ghidra.app.services.AnalyzerType;
+import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.AddressSetView;
-import ghidra.program.model.lang.Processor;
-import ghidra.program.model.lang.Register;
-import ghidra.program.model.lang.RegisterValue;
-import ghidra.program.model.listing.Function;
-import ghidra.program.model.listing.FunctionIterator;
-import ghidra.program.model.listing.Instruction;
-import ghidra.program.model.listing.InstructionIterator;
-import ghidra.program.model.listing.Program;
+import ghidra.program.model.lang.*;
+import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
@@ -53,9 +45,8 @@ public HCS12ConventionAnalyzer() {
public boolean canAnalyze(Program program) {
// Only analyze HCS-12 / HCS-12X Programs
Processor processor = program.getLanguage().getProcessor();
- boolean canDo = "HCS-12".equals(processor.toString()) ||
- "HCS-12X".equals(processor.toString());
-
+ String procName = processor.toString();
+ boolean canDo = "HCS-12".equals(procName) || "HCS-12X".equals(procName);
if (canDo) {
xgate = program.getRegister("XGATE");
}
@@ -64,12 +55,13 @@ public boolean canAnalyze(Program program) {
}
void checkReturn(Program program, Instruction instr) {
- String mnemonic = instr.getMnemonicString().toLowerCase();
if (instr == null || !instr.getFlowType().isTerminal()) {
return;
}
+ String mnemonic = instr.getMnemonicString().toLowerCase();
+
// if XGATE set on instruction is XGATE
RegisterValue xgateValue = program.getProgramContext().getRegisterValue(xgate, instr.getMinAddress());
if (xgateValue != null && xgateValue.hasValue() && xgateValue.getUnsignedValue().equals(BigInteger.ONE)) {
diff --git a/Ghidra/Processors/MCS96/data/languages/MCS96.sinc b/Ghidra/Processors/MCS96/data/languages/MCS96.sinc
index b75988fac01..2ac6ab06503 100644
--- a/Ghidra/Processors/MCS96/data/languages/MCS96.sinc
+++ b/Ghidra/Processors/MCS96/data/languages/MCS96.sinc
@@ -617,11 +617,7 @@ macro setSignedShiftRightCarryFlag(shiftee,amount) {
:CMPL dlreg, lreg is op8=0xc5; lreg; dlreg {
op1:4 = lreg;
tmp:4 = dlreg - op1;
- $(N) = (tmp s< 0);
- $(Z) = (tmp == 0);
- $(C) = scarry(dlreg, op1);
- $(V) = (((dlreg & ~op1 & ~tmp) | (~dlreg & op1 & tmp)) & 0x80000000 ) != 0;
- $(VT) = $(VT) | $(V);
+ subtractFlags(dlreg, op1, tmp);
}
@endif
diff --git a/Ghidra/Processors/MIPS/data/languages/mips.ldefs b/Ghidra/Processors/MIPS/data/languages/mips.ldefs
index cc6190b54eb..59dc5bba942 100644
--- a/Ghidra/Processors/MIPS/data/languages/mips.ldefs
+++ b/Ghidra/Processors/MIPS/data/languages/mips.ldefs
@@ -132,7 +132,7 @@
-
+