diff --git a/Ghidra/Debug/Debugger-agent-lldb/data/support/lldbsetuputils.ps1 b/Ghidra/Debug/Debugger-agent-lldb/data/support/lldbsetuputils.ps1 index 75d05c17e1..d68da53697 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/data/support/lldbsetuputils.ps1 +++ b/Ghidra/Debug/Debugger-agent-lldb/data/support/lldbsetuputils.ps1 @@ -81,11 +81,11 @@ function Compute-Lldb-Usermode-Args { function Compute-Lldb-Platform-Args { param($TargetImage, $TargetType, $TargetUrl, $RmiAddress) - $argslist = @("`"$Env:OPT_LLDB_PATH`"") + $arglist = @("`"$Env:OPT_LLDB_PATH`"") Add-Lldb-Init-Args -ArgList ([ref]$arglist) - $argslist+=("-o", "`"platform select '$TargetType'`"") - $argslist+=("-o", "`"platform connect '$TargetUrl'`"") - Add-Lldb-Image-And-Args -ArgList ([ref]$arglistt) -TargetImage $TargetImage -TargetArgs $Env:OPT_TARGET_ARGS + $arglist+=("-o", "`"platform select '$TargetType'`"") + $arglist+=("-o", "`"platform connect '$TargetUrl'`"") + Add-Lldb-Image-And-Args -ArgList ([ref]$arglist) -TargetImage $TargetImage -TargetArgs $Env:OPT_TARGET_ARGS Add-Lldb-Connect-And-Sync -ArgList ([ref]$arglist) -Address $RmiAddress Add-Lldb-Start-If-Image -ArgList ([ref]$arglist) -TargetImage $TargetImage Add-Lldb-Tail-Args -ArgList ([ref]$arglist) diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/certification.manifest b/Ghidra/Extensions/SymbolicSummaryZ3/certification.manifest index f064b42140..25445a7ec5 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/certification.manifest +++ b/Ghidra/Extensions/SymbolicSummaryZ3/certification.manifest @@ -3,3 +3,4 @@ Module.manifest||GHIDRA||||END| README.md||GHIDRA||||END| data/symbolic.summary.z3.theme.properties||GHIDRA||||END| +extension.properties||GHIDRA||||END| diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/extension.properties b/Ghidra/Extensions/SymbolicSummaryZ3/extension.properties new file mode 100644 index 0000000000..d88c2b756d --- /dev/null +++ b/Ghidra/Extensions/SymbolicSummaryZ3/extension.properties @@ -0,0 +1,5 @@ +name=Symbolic Summarizer (Z3) +description=Plugin for emulating with Z3 symbols and displaying a summary +author=Ghidra Team et al +createdOn=6/26/2025 +version=@extversion@ diff --git a/Ghidra/Features/Base/ghidra_scripts/ResolveX86orX64LinuxSyscallsScript.java b/Ghidra/Features/Base/ghidra_scripts/ResolveX86orX64LinuxSyscallsScript.java index 75bb5dbd53..d4378ac49b 100644 --- a/Ghidra/Features/Base/ghidra_scripts/ResolveX86orX64LinuxSyscallsScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/ResolveX86orX64LinuxSyscallsScript.java @@ -184,6 +184,10 @@ public class ResolveX86orX64LinuxSyscallsScript extends GhidraScript { funcName = syscallNumbersToNames.get(offset); } callee = createFunction(callTarget, funcName); + if (callee == null) { + Msg.warn(this, "Unable to create function at "+callTarget); + continue; + } callee.setCallingConvention(callingConvention); //check if the function name is one of the non-returning syscalls diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManagerListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManagerListener.java index 66f78dd86c..957bacf6c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManagerListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManagerListener.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,6 +15,7 @@ */ package ghidra.app.plugin.core.analysis; +@FunctionalInterface public interface AutoAnalysisManagerListener { public void analysisEnded(AutoAnalysisManager manager, boolean isCancelled); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java index a898297c02..87b7887196 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java @@ -17,7 +17,7 @@ package ghidra.app.plugin.core.console; import java.awt.*; import java.awt.event.*; -import java.io.PrintWriter; +import java.io.*; import javax.swing.*; import javax.swing.text.*; @@ -29,6 +29,7 @@ import docking.widgets.FindDialog; import docking.widgets.TextComponentSearcher; import generic.theme.GIcon; import generic.theme.Gui; +import ghidra.app.script.DecoratingPrintWriter; import ghidra.app.services.*; import ghidra.app.util.HelpTopics; import ghidra.framework.main.ConsoleTextPane; @@ -95,8 +96,8 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement } void init() { - stderr = new PrintWriter(new ConsoleWriter(this, true)); - stdin = new PrintWriter(new ConsoleWriter(this, false)); + stderr = new ConsolePrintWriter(true); + stdin = new ConsolePrintWriter(false); /* call this before build() -- we get our Font here */ setVisible(true); @@ -230,6 +231,11 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement textPane.addPartialMessage(msg); } + public void print(String msg, Color c) { + checkVisible(); + textPane.addPartialMessage(msg, c); + } + @Override public void printError(String errmsg) { checkVisible(); @@ -330,6 +336,84 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter implement // Inner Classes //================================================================================================= + private class ConsolePrintWriter extends DecoratingPrintWriter { + + private ColoringConsoleWriter writer; + + ConsolePrintWriter(boolean error) { + this(new ColoringConsoleWriter(error)); + } + + private ConsolePrintWriter(ColoringConsoleWriter writer) { + super(writer); + this.writer = writer; + } + + @Override + public void println(String s, Color c) { + try { + writer.setColor(c); + print(s); + println(); + } + finally { + writer.setColor(null); + } + } + + @Override + public void print(String s, Color c) { + try { + writer.setColor(c); + print(s); + } + finally { + writer.setColor(null); + } + } + } + + private class ColoringConsoleWriter extends Writer { + + private Color color; + private boolean error; + + public ColoringConsoleWriter(boolean error) { + this.error = error; + } + + void setColor(Color color) { + this.color = color; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + String s = new String(cbuf, off, len); + if (error) { + printError(s); + return; + } + + if (color == null) { + print(s); + return; + } + + print(s, color); + } + + @Override + public void flush() throws IOException { + // stub + } + + @Override + public void close() throws IOException { + clearMessages(); + } + + } + private class GoToMouseListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleWriter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleWriter.java deleted file mode 100644 index f408cea578..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleWriter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* ### - * IP: GHIDRA - * REVIEWED: YES - * - * 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.console; - -import ghidra.app.services.ConsoleService; - -import java.io.IOException; -import java.io.Writer; - -class ConsoleWriter extends Writer { - private ConsoleService console; - private boolean error; - - ConsoleWriter(ConsoleService console, boolean error) { - super(); - this.console = console; - this.error = error; - } - - /** - * @see java.io.Writer#close() - */ - @Override - public void close() throws IOException { - console.clearMessages(); - } - - /** - * @see java.io.Writer#flush() - */ - @Override - public void flush() throws IOException { - } - - /** - * @see java.io.Writer#write(char[], int, int) - */ - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - String str = new String(cbuf, off, len); - if (error) { - console.printError(str); - } - else { - console.print(str); - } - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java index a8402e26eb..4de7995424 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.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. @@ -35,7 +35,7 @@ import ghidra.app.plugin.core.console.CodeCompletion; */ public class CodeCompletionWindow extends JDialog { - private static final String FONT_ID = "font.plugin.terminal.completion.list"; + static final String FONT_ID = "font.plugin.terminal.completion.list"; protected final InterpreterPanel console; protected final JTextPane outputTextField; @@ -394,6 +394,10 @@ class CodeCompletionListSelectionModel extends DefaultListSelectionModel { */ class CodeCompletionListCellRenderer extends GListCellRenderer { + CodeCompletionListCellRenderer() { + setBaseFontId(CodeCompletionWindow.FONT_ID); + } + @Override protected String getItemText(CodeCompletion value) { return value.getDescription(); @@ -421,7 +425,6 @@ class CodeCompletionListCellRenderer extends GListCellRenderer { } component.setEnabled(list.isEnabled()); - component.setFont(list.getFont()); component.setComponentOrientation(list.getComponentOrientation()); Border border = null; if (cellHasFocus) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java index 9a2f8cf60e..a23ec0db5c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java @@ -26,11 +26,14 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.*; import javax.swing.text.*; +import org.apache.commons.io.output.WriterOutputStream; + import docking.DockingUtils; import docking.actions.KeyBindingUtils; import generic.theme.*; import generic.util.WindowUtilities; import ghidra.app.plugin.core.console.CodeCompletion; +import ghidra.app.script.DecoratingPrintWriter; import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; @@ -69,8 +72,12 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { /* junit */ IPStdin stdin; private OutputStream stdout; private OutputStream stderr; - private PrintWriter outWriter; - private PrintWriter errWriter; + private InterpreterPrintWriter outWriter; + private InterpreterPrintWriter errWriter; + + private AnsiRenderer stdErrRenderer = new AnsiRenderer(); + private AnsiRenderer stdInRenderer = new AnsiRenderer(); + private AnsiRenderer stdOutRenderer = new AnsiRenderer(); private SimpleAttributeSet STDOUT_SET; private SimpleAttributeSet STDERR_SET; @@ -129,11 +136,12 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { outputScrollPane.setFocusable(false); promptTextPane.setFocusable(false); + outWriter = new InterpreterPrintWriter(TextType.STDOUT); + errWriter = new InterpreterPrintWriter(TextType.STDERR); + stdin = new IPStdin(); - stdout = new IPOut(TextType.STDOUT); - stderr = new IPOut(TextType.STDERR); - outWriter = new PrintWriter(stdout, true); - errWriter = new PrintWriter(stderr, true); + stdout = outWriter.asOutputStream(); + stderr = errWriter.asOutputStream(); outputTextPane.setEditable(false); promptTextPane.setEditable(false); @@ -270,7 +278,6 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { private void updateFontAttributes(Font font) { Font boldFont = font.deriveFont(Font.BOLD); - STDOUT_SET = new GAttributes(font, NORMAL_COLOR); STDOUT_SET = new GAttributes(font, NORMAL_COLOR); STDERR_SET = new GAttributes(font, ERROR_COLOR); STDIN_SET = new GAttributes(boldFont, NORMAL_COLOR); @@ -410,11 +417,7 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { outputTextPane.setCaretPosition(Math.max(0, outputTextPane.getDocument().getLength())); } - AnsiRenderer stdErrRenderer = new AnsiRenderer(); - AnsiRenderer stdInRenderer = new AnsiRenderer(); - AnsiRenderer stdOutRenderer = new AnsiRenderer(); - - void addText(String text, TextType type) { + private void addText(String text, TextType type) { SimpleAttributeSet attributes; AnsiRenderer renderer; switch (type) { @@ -438,29 +441,23 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { repositionScrollpane(); } catch (BadLocationException e) { - Msg.error(this, "internal document positioning error", e); + // shouldn't happen + Msg.error(this, "Document positioning error", e); } } - private class IPOut extends OutputStream { - TextType type; - byte[] buffer = new byte[1]; + private void addText(String text, Color c) { - IPOut(TextType type) { - this.type = type; + SimpleAttributeSet attributes = new GAttributes(getFont(), c); + + try { + StyledDocument document = outputTextPane.getStyledDocument(); + stdOutRenderer.renderString(document, text, attributes); + repositionScrollpane(); } - - @Override - public void write(int b) throws IOException { - buffer[0] = (byte) b; - String text = new String(buffer); - addText(text, type); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - String text = new String(b, off, len); - addText(text, type); + catch (BadLocationException e) { + // shouldn't happen + Msg.error(this, "Document positioning error", e); } } @@ -565,6 +562,102 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener { // Inner Classes //================================================================================================== + private class InterpreterPrintWriter extends DecoratingPrintWriter { + + private InterpreterConsoleWriter writer; + + InterpreterPrintWriter(TextType type) { + this(new InterpreterConsoleWriter(type)); + } + + private InterpreterPrintWriter(InterpreterConsoleWriter writer) { + super(writer); + this.writer = writer; + } + + OutputStream asOutputStream() { + try { + return WriterOutputStream.builder().setWriter(writer).getOutputStream(); + } + catch (IOException e) { + Msg.error(this, "Unable to create output stream", e); + return null; + } + } + + @Override + public void println(String s, Color c) { + try { + writer.setColor(c); + print(s); + println(); + } + finally { + writer.setColor(null); + } + } + + @Override + public void print(String s, Color c) { + try { + writer.setColor(c); + print(s); + } + finally { + writer.setColor(null); + } + } + } + + private class InterpreterConsoleWriter extends Writer { + + private Color color; + + TextType type; + byte[] buffer = new byte[1]; + + public InterpreterConsoleWriter(TextType type) { + this.type = type; + } + + void setColor(Color color) { + this.color = color; + } + + @Override + public void write(int b) throws IOException { + buffer[0] = (byte) b; + String text = new String(buffer); + if (color != null) { + addText(text, color); + return; + } + + addText(text, type); + } + + @Override + public void write(char[] b, int off, int len) throws IOException { + String text = new String(b, off, len); + if (color != null) { + addText(text, color); + return; + } + + addText(text, type); + } + + @Override + public void flush() throws IOException { + // stub + } + + @Override + public void close() throws IOException { + clear(); + } + } + /** * An {@link InputStream} that has as its source text strings being pushed into * it by a thread, and being read by another thread. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java index cf3adad9eb..04a45aabd7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.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. @@ -487,7 +487,6 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { // this will overwrite any changes--be sure to resolve that before calling this method! try { loadScript(scriptSourceFile); - fileHash = MD5Utilities.getMD5Hash(scriptSourceFile.getFile(false)); clearChanges(); refreshAction(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolNode.java index 60af0f19b4..68797e39d1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolNode.java @@ -277,6 +277,9 @@ public class SymbolNode extends SymbolTreeNode { if (this == o) { return true; } + if (o == null) { + return false; + } if (getClass() != o.getClass()) { return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/DecoratingPrintWriter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/DecoratingPrintWriter.java new file mode 100644 index 0000000000..35d4542aea --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/DecoratingPrintWriter.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.script; + +import java.awt.Color; +import java.io.PrintWriter; +import java.io.Writer; + +/** + * A print writer that allows clients to specify the text color. + */ +public abstract class DecoratingPrintWriter extends PrintWriter { + + public DecoratingPrintWriter(Writer out) { + super(out); + } + + /** + * Print a line of text with the given color. + * @param s the text + * @param c the color + */ + public abstract void println(String s, Color c); + + /** + * Print text with the given color. + * @param s the text + * @param c the color + */ + public abstract void print(String s, Color c); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index 987ba38f99..53c831b149 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -1042,6 +1042,58 @@ public abstract class GhidraScript extends FlatProgramAPI { } } + /** + * Prints the {@link #decorateOutput optionally} {@link #decorate(String) decorated} message + * followed by a line feed to this script's {@code stdout} {@link PrintWriter}, which is set by + * {@link #set(GhidraState, ScriptControls)}. + *

+ * Additionally, the always {@link #decorate(String) decorated} message is written to Ghidra's + * log. + * + * @param message the message to print + * @param color the color for the text + */ + public void println(String message, Color color) { + + String decoratedMessage = decorate(message); + + Msg.info(GhidraScript.class, new ScriptMessage(decoratedMessage)); + + if (writer instanceof DecoratingPrintWriter scriptWriter) { + scriptWriter.println(decorateOutput ? decoratedMessage : message, color); + return; + } + + if (writer != null) { + writer.println(decorateOutput ? decoratedMessage : message); + } + } + + /** + * Prints the undecorated message with no newline to this script's {@code stdout} + * {@link PrintWriter}, which is set by {@link #set(GhidraState, ScriptControls)}. + *

+ * Additionally, the undecorated message is written to Ghidra's log. + * + * @param message the message to print + * @param color the color for the text + */ + public void print(String message, Color color) { + + String decoratedMessage = decorate(message); + + Msg.info(GhidraScript.class, new ScriptMessage(decoratedMessage)); + + if (writer instanceof DecoratingPrintWriter scriptWriter) { + scriptWriter.print(decorateOutput ? decoratedMessage : message, color); + return; + } + + if (writer != null) { + writer.print(decorateOutput ? decoratedMessage : message); + } + } + /** * Prints the undecorated {@link java.util.Formatter formatted message} to this script's * {@code stdout} {@link PrintWriter}, which is set by diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java index a0be99ca14..d89b57a5e9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptControls.java @@ -97,7 +97,7 @@ public class ScriptControls { * @param monitor A cancellable monitor */ public ScriptControls(InterpreterConsole console, TaskMonitor monitor) { - this(console.getStdOut(), console.getStdErr(), monitor); + this(console.getOutWriter(), console.getErrWriter(), monitor); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java index 4f3cfad23d..9308981536 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ConsoleService.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,11 +15,11 @@ */ package ghidra.app.services; +import java.io.PrintWriter; + import ghidra.app.plugin.core.console.ConsolePlugin; import ghidra.framework.plugintool.ServiceInfo; -import java.io.PrintWriter; - /** * Generic console interface allowing any plugin to print * messages to console window. @@ -109,8 +109,6 @@ public interface ConsoleService { * please throw {@link UnsupportedOperationException}. * * @return number of characters >= 0 - * - * @throws UnsupportedOperationException */ public int getTextLength(); @@ -128,8 +126,6 @@ public interface ConsoleService { * @param length the length of the desired string >= 0 * * @return the text, in a String of length >= 0 - * - * @throws UnsupportedOperationException */ public String getText(int offset, int length); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java index c0c6a13477..02ba8459c1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/elf/ElfHeader.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. @@ -1606,14 +1606,13 @@ public class ElfHeader implements StructConverter { * @return the section header that contains the address */ public ElfSectionHeader getSectionLoadHeaderContaining(long address) { -// FIXME: verify for (ElfSectionHeader sectionHeader : sectionHeaders) { if (!sectionHeader.isAlloc()) { continue; } long start = sectionHeader.getAddress(); long end = start + sectionHeader.getSize(); - if (start <= address && address <= end) { + if (start <= address && address < end) { return sectionHeader; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java index 64d90ff0a0..4b5585254b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/gui/MemorySearchOptionsPanel.java @@ -114,8 +114,9 @@ class MemorySearchOptionsPanel extends JPanel { innerPanel.add(label); Integer[] decimalSizes = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 16 }; + int decimalByteSize = model.getDecimalByteSize(); decimalByteSizeCombo = new GComboBox<>(decimalSizes); - decimalByteSizeCombo.setSelectedItem(4); + decimalByteSizeCombo.setSelectedItem(decimalByteSize); decimalByteSizeCombo.addItemListener(this::byteSizeComboChanged); decimalByteSizeCombo.setToolTipText("Size of decimal values in bytes"); innerPanel.add(decimalByteSizeCombo); @@ -126,6 +127,7 @@ class MemorySearchOptionsPanel extends JPanel { "Sets whether decimal values should be interpreted as unsigned values"); decimalUnsignedCheckbox.addActionListener( e -> model.setDecimalUnsigned(decimalUnsignedCheckbox.isSelected())); + decimalUnsignedCheckbox.setSelected(model.isDecimalUnsigned()); panel.add(decimalUnsignedCheckbox); return panel; @@ -205,9 +207,10 @@ class MemorySearchOptionsPanel extends JPanel { Charset[] supportedCharsets = { StandardCharsets.US_ASCII, StandardCharsets.UTF_8, StandardCharsets.UTF_16 }; + Charset charSet = model.getStringCharset(); charsetCombo = new GComboBox<>(supportedCharsets); charsetCombo.setName("Encoding Options"); - charsetCombo.setSelectedIndex(0); + charsetCombo.setSelectedItem(charSet); charsetCombo.addItemListener(this::encodingComboChanged); charsetCombo.setToolTipText("Character encoding for translating strings to bytes"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java index 63336c8dff..5bf837a2e2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/ByteMatcher.java @@ -27,14 +27,23 @@ import ghidra.features.base.memsearch.gui.SearchSettings; */ public abstract class ByteMatcher { + private final String name; private final String input; private final SearchSettings settings; - protected ByteMatcher(String input, SearchSettings settings) { + protected ByteMatcher(String name, String input, SearchSettings settings) { + this.name = name; this.input = input; this.settings = settings; } + /** + * {@return the name of this byte matcher.} + */ + public String getName() { + return name; + } + /** * Returns the original input text that generated this ByteMatacher. * @return the original input text that generated this BytesMatcher @@ -120,7 +129,10 @@ public abstract class ByteMatcher { /** * Record class to contain a match specification. + * @param start the offset in the buffer where the match starts + * @param length the length of the match + * @param matcher the matcher the found the match */ - public record ByteMatch(int start, int length) {} + public record ByteMatch(int start, int length, ByteMatcher matcher) {} } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/CombinedByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/CombinedByteMatcher.java new file mode 100644 index 0000000000..5a78a596db --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/CombinedByteMatcher.java @@ -0,0 +1,98 @@ +/* ### + * 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.features.base.memsearch.matcher; + +import java.util.Iterator; +import java.util.List; + +import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence; +import ghidra.features.base.memsearch.gui.SearchSettings; +import ghidra.features.base.memsearch.searcher.MemorySearcher; + +/** + * A ByteMatcher that searches an input sequence for matches from multiple patterns. This is + * useful for using with the {@link MemorySearcher} so that multiple patterns can be searched with + * only one pass through memory, thus paying the memory I/O costs only once. The resulting matches + * will contain the sub ByteMatcher that matched so that it is easy to know which of the multiple + * patterns matched. + */ +public class CombinedByteMatcher extends ByteMatcher { + + private List matchers; + + public CombinedByteMatcher(List matchers, SearchSettings settings) { + super("Multi-Pattern Matcher", null, settings); + this.matchers = matchers; + } + + @Override + public Iterable match(ExtendedByteSequence bytes) { + return new MultiMatcherIterator(bytes); + } + + @Override + public String getDescription() { + return getName(); + } + + @Override + public String getToolTip() { + return null; + } + + private class MultiMatcherIterator implements Iterable, Iterator { + + private Iterator matcherIterator; + private Iterator currentMatchIterator; + private ExtendedByteSequence bytes; + + MultiMatcherIterator(ExtendedByteSequence bytes) { + this.bytes = bytes; + matcherIterator = matchers.iterator(); + currentMatchIterator = getNextMatchIterator(); + } + + @Override + public boolean hasNext() { + while (currentMatchIterator != null && !currentMatchIterator.hasNext()) { + currentMatchIterator = getNextMatchIterator(); + } + return currentMatchIterator != null; + } + + private Iterator getNextMatchIterator() { + if (matcherIterator.hasNext()) { + ByteMatcher matcher = matcherIterator.next(); + return matcher.match(bytes).iterator(); + } + return null; + } + + @Override + public ByteMatch next() { + if (hasNext()) { + return currentMatchIterator.next(); + } + return null; + } + + @Override + public Iterator iterator() { + return this; + } + + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java index ecea33ae32..ae0d741f24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/InvalidByteMatcher.java @@ -50,7 +50,7 @@ public class InvalidByteMatcher extends ByteMatcher { * a negative number. */ public InvalidByteMatcher(String errorMessage, boolean isValidInput) { - super(null, null); + super("Invalid", null, null); this.errorMessage = errorMessage; this.isValidInput = isValidInput; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java index 0d587af996..b63451556e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/MaskedByteSequenceByteMatcher.java @@ -52,7 +52,7 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher { */ public MaskedByteSequenceByteMatcher(String input, byte[] bytes, byte[] masks, SearchSettings settings) { - super(input, settings); + super("Masked Byte Sequence Matcher", input, settings); if (masks == null) { masks = new byte[bytes.length]; @@ -145,7 +145,8 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher { while (nextPossibleStart >= 0) { startIndex = nextPossibleStart + 1; if (isValidMatch(nextPossibleStart)) { - return new ByteMatch(nextPossibleStart, searchBytes.length); + return new ByteMatch(nextPossibleStart, searchBytes.length, + MaskedByteSequenceByteMatcher.this); } nextPossibleStart = findNextPossibleStart(startIndex); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java index 99f850e03f..21a7449682 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/matcher/RegExByteMatcher.java @@ -32,7 +32,11 @@ public class RegExByteMatcher extends ByteMatcher { private final Pattern pattern; public RegExByteMatcher(String input, SearchSettings settings) { - super(input, settings); + this("Regex Matcher", input, settings); + } + + public RegExByteMatcher(String name, String input, SearchSettings settings) { + super(name, input, settings); // without DOTALL mode, bytes that match line terminator characters will cause // the regular expression pattern to not match. this.pattern = Pattern.compile(input, Pattern.DOTALL); @@ -133,7 +137,7 @@ public class RegExByteMatcher extends ByteMatcher { if (start >= byteSequence.getLength()) { return null; } - return new ByteMatch(start, end - start); + return new ByteMatch(start, end - start, RegExByteMatcher.this); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java index fc3a64e8b2..30a46bd018 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/searcher/MemorySearcher.java @@ -246,7 +246,7 @@ public class MemorySearcher { for (ByteMatch byteMatch : matcher.match(searchSequence)) { Address address = searchBytes.getAddress(byteMatch.start()); byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length()); - MemoryMatch match = new MemoryMatch(address, bytes, matcher); + MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher()); if (filter.test(match)) { return match; } @@ -269,7 +269,7 @@ public class MemorySearcher { for (ByteMatch byteMatch : matcher.match(searchSequence)) { Address address = searchBytes.getAddress(byteMatch.start()); byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length()); - MemoryMatch match = new MemoryMatch(address, bytes, matcher); + MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher()); if (filter.test(match)) { last = match; } @@ -316,7 +316,7 @@ public class MemorySearcher { for (ByteMatch byteMatch : matcher.match(searchSequence)) { Address address = searchBytes.getAddress(byteMatch.start()); byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length()); - MemoryMatch match = new MemoryMatch(address, bytes, matcher); + MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher()); if (filter.test(match)) { if (accumulator.size() >= searchLimit) { return false; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java index 0898a3b0bd..9bc6df8b2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java @@ -15,8 +15,10 @@ */ package ghidra.framework.main; +import java.awt.Color; import java.awt.Font; import java.util.LinkedList; +import java.util.Objects; import javax.swing.JTextPane; import javax.swing.text.*; @@ -83,6 +85,10 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener doAddMessage(new MessageWrapper(message)); } + public void addPartialMessage(String message, Color c) { + doAddMessage(new MessageWrapper(message, getFont(), c)); + } + public void addErrorMessage(String message) { doAddMessage(new ErrorMessage(message)); } @@ -280,15 +286,21 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener //================================================================================================== private static class MessageWrapper { - private final StringBuilder message; + protected final StringBuilder message; + private Color color; + private Font font; private MessageWrapper(String message) { - if (message == null) { - throw new AssertException("Attempted to log a null message."); - } + Objects.requireNonNull(message, "Attempted to log a null message"); this.message = new StringBuilder(message); } + public MessageWrapper(String message, Font font, Color color) { + this(message); + this.font = Objects.requireNonNull(font); + this.color = Objects.requireNonNull(color); + } + CharSequence getMessage() { return message; } @@ -297,13 +309,31 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener if (getClass() != other.getClass()) { return false; } + + if (!Objects.equals(color, other.color)) { + return false; + } + message.append(other.message); return true; } AttributeSet getAttributes() { + if (color != null) { + GAttributes attrs = new GAttributes(font, color); + attrs.addAttribute(CUSTOM_ATTRIBUTE_KEY, OUTPUT_ATTRIBUTE_VALUE); + return attrs; + } return outputAttributes; } + + @Override + public String toString() { + if (color == null) { + return message.toString(); + } + return "[color=" + color + "] " + message.toString(); + } } private static class ErrorMessage extends MessageWrapper { @@ -315,6 +345,10 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener AttributeSet getAttributes() { return errorAttributes; } - } + @Override + public String toString() { + return "[error] " + message.toString(); + } + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java index 0f574db55a..2c24b53ada 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java @@ -538,7 +538,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio DockingWindowManager.setMouseOverAction(windowMenuAction); performLaunchKeyStrokeDialogAction(); - DialogComponentProvider warningDialog = waitForDialogComponent("Unable to Set Keybinding"); + DialogComponentProvider warningDialog = waitForDialogComponent("Unable to Set Key Binding"); close(warningDialog); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index e5a99e216b..aa3ba83861 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -1199,6 +1199,34 @@ public abstract class AbstractGhidraScriptMgrPluginTest assertTrue("Timed-out waiting for cancelled script to complete", success); } + protected void runScript(ResourceFile scriptFile) throws Exception { + + GhidraScriptProvider scriptProvider = GhidraScriptUtil.getProvider(scriptFile); + GhidraScript script = + scriptProvider.getScriptInstance(scriptFile, new PrintWriter(System.err)); + + Task task = new RunScriptTask(script, plugin.getCurrentState(), console); + task.addTaskListener(provider.getTaskListener()); + + CountDownLatch latch = new CountDownLatch(1); + task.addTaskListener(new TaskListener() { + + @Override + public void taskCompleted(Task t) { + latch.countDown(); + } + + @Override + public void taskCancelled(Task t) { + latch.countDown(); + } + }); + + TaskLauncher.launch(task); + + latch.await(TASK_RUN_SCRIPT_TIMEOUT_SECS, TimeUnit.SECONDS); + } + protected void startRunScriptTask(GhidraScript script) throws Exception { Task task = new RunScriptTask(script, plugin.getCurrentState(), console); task.addTaskListener(provider.getTaskListener()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java index f1d9b9a1f4..bcd2ea0ddf 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin2Test.java @@ -17,9 +17,12 @@ package ghidra.app.plugin.core.script; import static org.junit.Assert.*; +import java.awt.Color; import java.io.*; import java.nio.file.Path; +import javax.swing.text.*; + import org.apache.logging.log4j.Level; import org.junit.Test; @@ -490,6 +493,113 @@ public class GhidraScriptMgrPlugin2Test extends AbstractGhidraScriptMgrPluginTes "*2*", output); } + @Test + public void testScriptPrintWithColor() throws Exception { + + // create a script + ResourceFile newScriptFile = createTempScriptFile("LineColoringScript"); + String filename = newScriptFile.getName(); + String className = filename.replaceAll("\\.java", ""); + + String text1 = "This is black, "; + String text2 = "this is blue, and "; + String text3 = "this is red.\\n"; + String line2 = "This is the default color."; + + //@formatter:off + String newScript = """ + import ghidra.app.script.GhidraScript; + import java.awt.Color; + + public class %s extends GhidraScript { + + @Override + public void run() throws Exception { + + print("%s"); + print("%s", Color.BLUE); + print("%s", Color.RED); + print("%s"); + } + }; + """.formatted(className, text1, text2, text3, line2); + //@formatter:on + + writeStringToFile(newScriptFile, newScript); + + runScript(newScriptFile); + waitForSwing(); + + assertConsoleTextColor(text1, Color.BLACK); + assertConsoleTextColor(text2, Color.BLUE); + assertConsoleTextColor(text3, Color.RED); + assertConsoleTextColor(text2, Color.BLACK); + } + + @Test + public void testScriptPrintlnWithColor() throws Exception { + + // create a script + ResourceFile newScriptFile = createTempScriptFile("LineColoringScript"); + String filename = newScriptFile.getName(); + String className = filename.replaceAll("\\.java", ""); + + String line1 = "1 This is a default line"; + String line2 = "2 This is a blue line"; + String line3 = "3 This is a red line"; + + //@formatter:off + String newScript = """ + import ghidra.app.script.GhidraScript; + import java.awt.Color; + + public class %s extends GhidraScript { + + @Override + public void run() throws Exception { + + println("%s"); + println("%s", Color.BLUE); + println("%s", Color.RED); + } + }; + """.formatted(className, line1, line2, line3); + //@formatter:on + + writeStringToFile(newScriptFile, newScript); + + runScript(newScriptFile); + waitForSwing(); + + assertConsoleTextColor(line1, Color.BLACK); + assertConsoleTextColor(line2, Color.BLUE); + assertConsoleTextColor(line3, Color.RED); + } + + private void assertConsoleTextColor(String text, Color expectedFgColor) { + String fullText = runSwing(() -> consoleTextPane.getText()); + + // We have 2 layers of newlines in the test. A '\\n' that gets written to file as Java + // code. That then gets compiled and written out as a newline. Our 'text' value passed + // here is that original '\\n'. We are trying to compare that against what ends up in the + // console, which has gone through 2 string interpretations to end up as a standard newline. + // Strip off the '\\n' from the original input text before looking for it in the console. + String visibleText = text.replaceAll("\\\\n", ""); + int start = fullText.indexOf(visibleText); + int end = visibleText.length(); + + runSwing(() -> { + StyledDocument styledDocument = (StyledDocument) consoleTextPane.getDocument(); + + for (int i = start; i < end; i++) { + Element element = styledDocument.getCharacterElement(i); + AttributeSet attrs = element.getAttributes(); + Color actualFgColor = (Color) attrs.getAttribute(StyleConstants.Foreground); + assertEquals(expectedFgColor, actualFgColor); + } + }); + } + private Path getBinDirFromScriptFile(ResourceFile sourceFile) { ResourceFile tmpSourceDir = sourceFile.getParentFile(); String tmpSymbolicName = GhidraSourceBundle.sourceDirHash(tmpSourceDir); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java index 03e80c4f71..409b94276d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java @@ -51,7 +51,6 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { private KeyBindingsPanel panel; private JTable table; private TableModel model; - private JTextField keyField; private JTextPane statusPane; private JDialog dialog; @@ -70,7 +69,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { setUpDialog(); - grabActionsWithoutKeybinding(); + grabActionsWithoutKeyBinding(); } @After @@ -82,8 +81,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testKeyBindingsDisplay() throws Exception { - assertEquals(3, model.getColumnCount()); - String[] ids = new String[] { "Action Name", "KeyBinding", "Plugin Name" }; + assertEquals(3, table.getColumnCount()); + String[] ids = new String[] { "Action Name", "Key Binding", "Owner" }; TableColumnModel m = table.getColumnModel(); for (int i = 0; i < ids.length; i++) { TableColumn c = m.getColumn(i); @@ -91,15 +90,9 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } assertTrue(model.getRowCount() > 0); - // look for the info panel - MultiLineLabel label = findComponent(panel, MultiLineLabel.class); - String str = "To add or change a key binding, select an action\n" + - "and type any key combination."; - - assertEquals(str, label.getLabel()); + assertMessage("Select an action to change a keybinding"); // verify that the description is displayed for the selected action - selectRowForAction(action1); String actualText = getText(statusPane); @@ -130,6 +123,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull("Could not find edit key binding action.", action); selectRowForAction(action); + JTextField keyField = getKeyField(); triggerText(keyField, "z"); assertKeyFieldText("Z"); @@ -148,14 +142,16 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } } - triggerText(keyField, "z"); - assertMessage("No action is selected."); + JTextField keyField = getKeyField(); + assertNull(keyField); + assertMessage("Select an action to change a keybinding"); } @Test public void testSetKeyBinding() throws Exception { // set a key binding on an action that does not have a key binding selectRowForAction(action1); + JTextField keyField = getKeyField(); triggerActionKey(keyField, InputEvent.CTRL_DOWN_MASK, KeyEvent.VK_X); KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK); assertKeyFieldText(KeyBindingUtils.parseKeyStroke(ks)); @@ -168,6 +164,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { public void testSetKeyBinding2() throws Exception { selectRowForAction(action1); + JTextField keyField = getKeyField(); triggerText(keyField, "x"); assertKeyFieldText("X"); @@ -394,6 +391,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } private void assertNoKeyStrokeText() { + JTextField keyField = getKeyField(); assertEquals("", keyField.getText()); } @@ -402,6 +400,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } private void assertKeyFieldText(String s) { + JTextField keyField = getKeyField(); assertEquals(s, runSwing(() -> keyField.getText())); } @@ -410,6 +409,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } private void typeKeyStroke(KeyStroke ks) { + JTextField keyField = getKeyField(); triggerKey(keyField, ks); waitForSwing(); } @@ -419,6 +419,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { } private void typeKeyStroke(int modifiers, int keyCode) { + JTextField keyField = getKeyField(); triggerKey(keyField, modifiers, keyCode, KeyEvent.CHAR_UNDEFINED); waitForSwing(); } @@ -538,14 +539,18 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { dialog.setVisible(true); }); table = findComponent(panel, JTable.class); - keyField = (JTextField) findComponentByName(panel, "Key Entry Text Field"); statusPane = findComponent(panel, JTextPane.class); model = table.getModel(); waitForSwing(); } + private JTextField getKeyField() { + JTextField keyField = (JTextField) findComponentByName(panel, "Key Entry Text Field"); + return keyField; + } + // find 2 actions that do not have key bindings so that we can add and change the values - private void grabActionsWithoutKeybinding() { + private void grabActionsWithoutKeyBinding() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { if (ignoreAction(action)) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java index 70c13ca757..a6a713d401 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java @@ -980,8 +980,6 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { selectRowForAction(panel, actionName, actionOwner); - setToggleButtonSelected(panel, "Enter Mouse Binding", true); - JPanel actionBindingPanel = (JPanel) getInstanceField("actionBindingPanel", panel); JTextField textField = (JTextField) getInstanceField("mouseEntryField", actionBindingPanel); @@ -1008,8 +1006,6 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { selectRowForAction(panel, actionName, actionOwner); - setToggleButtonSelected(panel, "Enter Mouse Binding", false); - JPanel actionBindingPanel = (JPanel) getInstanceField("actionBindingPanel", panel); KeyEntryPanel keyEntryPanel = (KeyEntryPanel) getInstanceField("keyEntryPanel", actionBindingPanel); @@ -1031,8 +1027,6 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { selectRowForAction(panel, actionName, actionOwner); - setToggleButtonSelected(panel, "Enter Mouse Binding", false); - pressButtonByName(panel, "Clear Key Binding"); waitForSwing(); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/CombinedByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/CombinedByteMatcherTest.java new file mode 100644 index 0000000000..10662bf8e7 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/CombinedByteMatcherTest.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.features.base.memsearch.bytesequence; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.stream.StreamSupport; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.features.base.memsearch.matcher.*; +import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch; + +public class CombinedByteMatcherTest { + private ByteMatcher xxxByteMatcher; + private ByteMatcher yyyByteMatcher; + private ByteMatcher zzzByteMatcher; + private CombinedByteMatcher multiMatcher; + + @Before + public void setUp() { + + xxxByteMatcher = new RegExByteMatcher("xxx", null); + yyyByteMatcher = new RegExByteMatcher("yyy", null); + zzzByteMatcher = new RegExByteMatcher("zzz", null); + multiMatcher = + new CombinedByteMatcher(List.of(xxxByteMatcher, yyyByteMatcher, zzzByteMatcher), null); + } + + @Test + public void textFindsOneEachPatterns() { + List results = match("fooxxxbar, fooyyybar, foozzzbar"); + assertEquals(3, results.size()); + assertEquals(new ByteMatch(3, 3, xxxByteMatcher), results.get(0)); + assertEquals(new ByteMatch(15, 3, yyyByteMatcher), results.get(1)); + assertEquals(new ByteMatch(26, 3, zzzByteMatcher), results.get(2)); + } + + @Test + public void textFindsMutliplePatterns() { + List results = match("xxxyyyzzzxxxyyyzzz"); + assertEquals(6, results.size()); + assertEquals(new ByteMatch(0, 3, xxxByteMatcher), results.get(0)); + assertEquals(new ByteMatch(9, 3, xxxByteMatcher), results.get(1)); + assertEquals(new ByteMatch(3, 3, yyyByteMatcher), results.get(2)); + assertEquals(new ByteMatch(12, 3, yyyByteMatcher), results.get(3)); + assertEquals(new ByteMatch(6, 3, zzzByteMatcher), results.get(4)); + assertEquals(new ByteMatch(15, 3, zzzByteMatcher), results.get(5)); + } + + @Test + public void testNoMatches() { + List results = match("There are no matches here!"); + assertEquals(0, results.size()); + } + + private List match(String s) { + ExtendedByteSequence sequence = createByteSequence(s); + Iterable match = multiMatcher.match(sequence); + return StreamSupport.stream(match.spliterator(), false).toList(); + } + + private ExtendedByteSequence createByteSequence(String s) { + ByteSequence main = new ByteArrayByteSequence(makeBytes(s)); + ByteSequence extra = new ByteArrayByteSequence(makeBytes("")); + return new ExtendedByteSequence(main, extra, 0); + } + + private byte[] makeBytes(String string) { + byte[] bytes = new byte[string.length()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) string.charAt(i); + } + return bytes; + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java index aaec032521..2999857918 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/MaskedBytesSequenceByteMatcherTest.java @@ -49,10 +49,10 @@ public class MaskedBytesSequenceByteMatcherTest { Iterator it = byteMatcher.match(byteSequence).iterator(); assertTrue(it.hasNext()); - assertEquals(new ByteMatch(2, 3), it.next()); + assertEquals(new ByteMatch(2, 3, byteMatcher), it.next()); assertTrue(it.hasNext()); - assertEquals(new ByteMatch(9, 3), it.next()); + assertEquals(new ByteMatch(9, 3, byteMatcher), it.next()); assertFalse(it.hasNext()); @@ -66,8 +66,8 @@ public class MaskedBytesSequenceByteMatcherTest { Iterator it = byteMatcher.match(byteSequence).iterator(); - assertEquals(new ByteMatch(2, 3), it.next()); - assertEquals(new ByteMatch(9, 3), it.next()); + assertEquals(new ByteMatch(2, 3, byteMatcher), it.next()); + assertEquals(new ByteMatch(9, 3, byteMatcher), it.next()); assertNull(it.next()); } @@ -81,9 +81,9 @@ public class MaskedBytesSequenceByteMatcherTest { Iterator it = byteMatcher.match(byteSequence).iterator(); - assertEquals(new ByteMatch(1, 3), it.next()); - assertEquals(new ByteMatch(6, 3), it.next()); - assertEquals(new ByteMatch(8, 3), it.next()); + assertEquals(new ByteMatch(1, 3, byteMatcher), it.next()); + assertEquals(new ByteMatch(6, 3, byteMatcher), it.next()); + assertEquals(new ByteMatch(8, 3, byteMatcher), it.next()); assertNull(it.next()); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java index ff027deffc..4c949db118 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/memsearch/bytesequence/RegExByteMatcherTest.java @@ -45,10 +45,10 @@ public class RegExByteMatcherTest { Iterator it = byteMatcher.match(byteSequence).iterator(); assertTrue(it.hasNext()); - assertEquals(new ByteMatch(4, 3), it.next()); + assertEquals(new ByteMatch(4, 3, byteMatcher), it.next()); assertTrue(it.hasNext()); - assertEquals(new ByteMatch(14, 3), it.next()); + assertEquals(new ByteMatch(14, 3, byteMatcher), it.next()); assertFalse(it.hasNext()); @@ -61,8 +61,8 @@ public class RegExByteMatcherTest { Iterator it = byteMatcher.match(byteSequence).iterator(); - assertEquals(new ByteMatch(4, 3), it.next()); - assertEquals(new ByteMatch(14, 3), it.next()); + assertEquals(new ByteMatch(4, 3, byteMatcher), it.next()); + assertEquals(new ByteMatch(14, 3, byteMatcher), it.next()); assertNull(it.next()); } diff --git a/Ghidra/Features/DecompilerDependent/certification.manifest b/Ghidra/Features/DecompilerDependent/certification.manifest index 7bada2609a..5e08aeb77e 100644 --- a/Ghidra/Features/DecompilerDependent/certification.manifest +++ b/Ghidra/Features/DecompilerDependent/certification.manifest @@ -8,3 +8,5 @@ src/main/help/help/topics/DecompilerTaint/DecompilerTaint.html||GHIDRA||||END| src/main/help/help/topics/DecompilerTextFinderPlugin/Decompiler_Text_Finder.html||GHIDRA||||END| src/main/help/help/topics/DecompilerTextFinderPlugin/images/DecompilerTextFinderDialog.png||GHIDRA||||END| src/main/help/help/topics/DecompilerTextFinderPlugin/images/DecompilerTextFinderResultsTable.png||GHIDRA||||END| +src/main/resources/images/default-query.png||GHIDRA||||END| +src/main/resources/images/gate-set.png||GHIDRA||||END| diff --git a/Ghidra/Features/DecompilerDependent/data/decompiler.dependent.theme.properties b/Ghidra/Features/DecompilerDependent/data/decompiler.dependent.theme.properties index d750ff76df..b5cba262ee 100644 --- a/Ghidra/Features/DecompilerDependent/data/decompiler.dependent.theme.properties +++ b/Ghidra/Features/DecompilerDependent/data/decompiler.dependent.theme.properties @@ -8,5 +8,7 @@ color.bg.decompiler.highlights.sourcesink = color.palette.darkcyan color.bg.decompiler.highlights.path = color.palette.yellow icon.plugin.decompiler.text.finder.select.functions = icon.make.selection {FunctionScope.gif[size(12,12)][move(6,6)]} +icon.plugin.decompiler.taint.gate.set = gate-set.png +icon.plugin.decompiler.taint.default.query = default-query.png [Dark Defaults] \ No newline at end of file diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/absint/AbstractInterpretationService.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/absint/AbstractInterpretationService.java new file mode 100644 index 0000000000..b4ad16a564 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/absint/AbstractInterpretationService.java @@ -0,0 +1,29 @@ +/* ### + * 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.decompiler.absint; + +import ghidra.framework.plugintool.ServiceInfo; + +/** + * The AbstractInterpretationService provides a general service for generating results from an external engine + */ +@ServiceInfo(description = "supply abstract interpretation") +public interface AbstractInterpretationService { + + public String getActiveQueryName(); + + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java index 5c1a13974e..0ca4c22bc7 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java @@ -33,6 +33,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.HighVariable; import ghidra.program.model.pcode.PcodeException; import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; import sarif.SarifService; /** @@ -59,10 +60,11 @@ public abstract class AbstractTaintState implements TaintState { protected TaintOptions taintOptions; protected TaintPlugin plugin; protected boolean usesIndex = true; - private boolean cancellation; + protected TaskMonitor monitor = TaskMonitor.DUMMY; private TaskType taskType = TaskType.SET_TAINT; + public AbstractTaintState(TaintPlugin plugin) { this.plugin = plugin; } @@ -83,13 +85,22 @@ public abstract class AbstractTaintState implements TaintState { protected abstract void writeFooter(PrintWriter writer); @Override - public boolean wasCancelled() { - return this.cancellation; + public void setMonitor(TaskMonitor monitor) { + if (monitor != null) { + monitor.setIndeterminate(true); + monitor.setShowProgressValue(false); + } + this.monitor = monitor; } @Override - public void setCancellation(boolean status) { - this.cancellation = status; + public boolean isCancelled() { + return monitor != null && monitor.isCancelled(); + } + + @Override + public void cancel() { + monitor.cancel(); } @Override @@ -320,6 +331,10 @@ public abstract class AbstractTaintState implements TaintState { pb.redirectError(Redirect.INHERIT); Process p = pb.start(); + monitor.addCancelledListener(() -> { + p.destroyForcibly(); + }); + readQueryResultsIntoDataFrame(program, p.getInputStream()); // We wait for the process to finish after starting to read the input stream, @@ -555,4 +570,9 @@ public abstract class AbstractTaintState implements TaintState { return ENGINE_NAME; } + @Override + public String getQueryName() { + return null; + } + } diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java index 98690229b6..d2b55f47cf 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java @@ -57,7 +57,7 @@ public class TaintDecompilerMarginProvider extends JPanel private Icon sourceIcon = new GIcon("icon.plugin.scriptmanager.run"); private Icon sinkIcon = new GIcon("icon.stop"); - private Icon gateIcon = new GIcon("icon.debugger.breakpoint.set"); + private Icon gateIcon = new GIcon("icon.plugin.decompiler.taint.gate.set"); public TaintDecompilerMarginProvider(TaintPlugin plugin) { this.plugin = plugin; diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java index e4a58b9ec7..e85966553e 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java @@ -160,15 +160,16 @@ public class TaintLabelsTableProvider extends ComponentProviderAdapter { TaintState state = plugin.getTaintState(); - Task queryTask = new Task("Source-Sink Query Task", true, true, true, true) { + Task queryTask = new Task("Source-Sink Query Task", true, false, false, true) { @Override public void run(TaskMonitor monitor) { - state.setCancellation(false); monitor.initialize(program.getFunctionManager().getFunctionCount()); // query index NOT the default query; use table data. boolean successful = state.queryIndex(currentProgram, tool, QueryType.SRCSINK); - state.setCancellation(!successful || monitor.isCancelled()); + if (!successful) { + state.cancel(); + } monitor.clearCancelled(); } }; @@ -180,7 +181,7 @@ public class TaintLabelsTableProvider extends ComponentProviderAdapter { // 1. Query Index. tool.execute(queryTask); - if (!state.wasCancelled()) { + if (!state.isCancelled()) { // 2. Show Table. SarifService sarifService = plugin.getSarifService(); sarifService.getController() @@ -192,8 +193,6 @@ public class TaintLabelsTableProvider extends ComponentProviderAdapter { TaintProvider provider = plugin.getProvider(); provider.setTaint(); plugin.consoleMessage("query complete"); - state.setCancellation(false); - } else { plugin.consoleMessage("Source-Sink query was cancelled."); diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java index c5bbccdbae..4e17637a75 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java @@ -265,7 +265,7 @@ public class TaintProvider extends ComponentProviderAdapter implements OptionsCh state.setTaskType(taskType); AddressSet taintAddressSet = state.getTaintAddressSet(); - Msg.info(this, "setTaint(): " + taintAddressSet.toString()); + //Msg.info(this, "setTaint(): " + taintAddressSet.toString()); // sets the selection in the LISTING? // TODO: should we not set select and only highlight in the decompilation. diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java index 22724c9bc7..c487a16c3d 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java @@ -119,6 +119,9 @@ public record TaintQueryResult(String name,String fqname, Address iaddr, Address if (fqname.contains(":"+hvName)) { return hvName; } + if (fqname.contains(":"+ast.getAddress())) { + return hvName; + } } } diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java index 12a4dd37cb..6cf2f51ad9 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java @@ -35,6 +35,7 @@ import ghidra.program.model.symbol.SymbolTable; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; +import ghidra.util.task.TaskMonitor; /** * The interface for the methods that collect desired taint information from the decompiler window and store them @@ -92,6 +93,8 @@ public interface TaintState extends ExtensionPoint { */ public boolean queryIndex(Program program, PluginTool tool, QueryType queryType); + public String getQueryName(); + public TaintLabel toggleMark(MarkType mtype, ClangToken token) throws PcodeException; public Set getTaintLabels(MarkType mtype); @@ -121,9 +124,11 @@ public interface TaintState extends ExtensionPoint { // predicate that indicates there are sources, sinks, or gates. public boolean hasMarks(); - public boolean wasCancelled(); + public void setMonitor(TaskMonitor monitor); - public void setCancellation(boolean status); + public boolean isCancelled(); + + public void cancel(); public void setTaintVarnodeMap(Map> vmap, TaskType delta); diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java index 52ee73f957..39fe4eb0bc 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java @@ -76,11 +76,9 @@ public abstract class TaintAbstractQueryAction extends TaintAbstractDecompilerAc @Override public void run(TaskMonitor monitor) { TaintState state = plugin.getTaintState(); - state.setCancellation(false); - monitor.initialize(program.getFunctionManager().getFunctionCount()); + state.setMonitor(monitor); state.queryIndex(program, tool, queryType); - state.setCancellation(monitor.isCancelled()); - monitor.clearCancelled(); + state.setMonitor(null); } }; @@ -91,11 +89,15 @@ public abstract class TaintAbstractQueryAction extends TaintAbstractDecompilerAc tool.execute(defaultQueryTask); TaintState state = plugin.getTaintState(); - if (!state.wasCancelled()) { + if (!defaultQueryTask.isCancelled()) { TaintFormat format = state.getOptions().getTaintOutputForm(); if (!format.equals(TaintFormat.NONE)) { SarifService sarifService = plugin.getSarifService(); sarifService.getController().setDefaultGraphHander(SarifTaintGraphRunHandler.class); + String queryName = state.getQueryName(); + if (queryName != null) { + desc = queryName; + } sarifService.showSarif(desc, state.getData()); } @@ -104,11 +106,9 @@ public abstract class TaintAbstractQueryAction extends TaintAbstractDecompilerAc provider.setTaint(); plugin.consoleMessage("query complete"); - state.setCancellation(false); } else { plugin.consoleMessage("Source-Sink query was cancelled."); - } } } diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java index 022c364e35..1d181c930e 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java @@ -28,7 +28,7 @@ public class TaintQueryDefaultAction extends TaintAbstractQueryAction { public TaintQueryDefaultAction(TaintPlugin plugin) { super(plugin, "DefaultQuery", "Default Taint Query", "Run default taint query"); - executeTaintQueryIconString = "icon.version.tracking.markup.status.conflict"; + executeTaintQueryIconString = "icon.plugin.decompiler.taint.default.query"; executeTaintQueryIcon = new GIcon(executeTaintQueryIconString); queryType = QueryType.DEFAULT; diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSetSizeAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSetSizeAction.java index c33f3b7c51..cb8c7991f2 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSetSizeAction.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSetSizeAction.java @@ -23,6 +23,7 @@ import ghidra.app.plugin.core.decompiler.taint.TaintLabel; import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; import ghidra.util.UndefinedFunction; /** @@ -46,6 +47,7 @@ public class TaintSetSizeAction extends TaintAbstractDecompilerAction { public TaintSetSizeAction(TaintPlugin plugin) { super("Set length"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, TaintPlugin.HELP_LOCATION)); // Taint Menu -> Source sub item. setPopupMenuData(new MenuData(new String[] { "Taint", "Set length" }, "Decompile")); this.plugin = plugin; diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java index ea5e94e8bf..41de9cdaab 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java @@ -216,6 +216,7 @@ public class SarifTaintResultHandler extends SarifResultHandler { protected void doRun(TaskMonitor monitor) { int[] selected = tableProvider.filterTable.getTable().getSelectedRows(); Map> map = new HashMap<>(); + AddressSet set = new AddressSet(); for (int row : selected) { Map r = tableProvider.getRow(row); String kind = (String) r.get("kind"); @@ -225,12 +226,17 @@ public class SarifTaintResultHandler extends SarifResultHandler { if (kind.equals("variable")) { getTaintedVariable(map, r); } + Address addr = (Address) r.get("Address"); + if (addr != null) { + set.add(addr); + } } PluginTool tool = tableProvider.getController().getPlugin().getTool(); TaintService service = tool.getService(TaintService.class); if (service != null) { service.setVarnodeMap(map, true, taskType); + service.setAddressSet(set, false); } } diff --git a/Ghidra/Features/DecompilerDependent/src/main/resources/images/default-query.png b/Ghidra/Features/DecompilerDependent/src/main/resources/images/default-query.png new file mode 100644 index 0000000000..b789c54462 Binary files /dev/null and b/Ghidra/Features/DecompilerDependent/src/main/resources/images/default-query.png differ diff --git a/Ghidra/Features/DecompilerDependent/src/main/resources/images/gate-set.png b/Ghidra/Features/DecompilerDependent/src/main/resources/images/gate-set.png new file mode 100644 index 0000000000..2d618a99a7 Binary files /dev/null and b/Ghidra/Features/DecompilerDependent/src/main/resources/images/gate-set.png differ diff --git a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_5_4.java b/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_5_4.java deleted file mode 100644 index 4128c0ec67..0000000000 --- a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_5_4.java +++ /dev/null @@ -1,246 +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. - */ -// VxWorksSymTab_5_4 is a copy of VxWorksSymTab_6_1 with a different value for SYM_ENTRY_SIZE -// It was replaced at the request of a customer who tested that it worked with the slight modification -// VxWorksSymTab_6_1 is an adaptation of the vxWorksSymTab script. It was modified by a customer -// to use a single loop, instead of two. It also added demangling of C++ symbol names - at least -// those that Ghidra knows how to demangle. -// -// Extracts all symbols in a VxWorks symbol table and disassembles -// the global functions. Any existing symbols in the Ghidra symbol table -// that collide with symbols defined in the VxWorks symbol table are deleted. -// -// The VxWorks symbol table is an array of symbol table entries [0..n-1] -// followed by a 32-bit value that is equal to n (number of sym tbl entries). -// Each entry in the array has the following structure: -// -// // Total size: 0x18 (24) bytes -// 0x00 int NULL -// 0x04 char *symNameAddr // symbol name -// 0x08 void *symLocAddr // location of object named by symbol -// 0x0c int NULL -// 0x10 int NULL -// 0x14 uchar symType // see switch statement below -// 0x15 uchar fill[3] -// -// The script requests: -// - Output file name: Each symbol name and address is recorded here. -// (Errors are also logged to this file.) -// - Address of "number of symbols" value: At the end of the symbol table, -// its length is recorded as a 32-bit integer. The -// script needs the address of that value to calculate -// the symbol table's start address. -// -// @category Customer Submission.vxWorks - -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.List; - -import ghidra.app.cmd.label.DemanglerCmd; -import ghidra.app.script.GhidraScript; -import ghidra.app.util.demangler.DemangledException; -import ghidra.app.util.demangler.MangledContext; -import ghidra.app.util.demangler.gnu.GnuDemangler; -import ghidra.program.model.address.Address; -import ghidra.program.model.mem.Memory; -import ghidra.program.model.symbol.*; - -public class VxWorksSymTab_5_4 extends GhidraScript { - - static final int SYM_ENTRY_SIZE = 16; - static final int SYM_NAME_OFF = 4; - static final int SYM_LOC_OFF = 8; - static final int SYM_TYPE_OFF = 0x14; - - @Override - public void run() throws Exception { - - // Get Memory and SymbolTable objects (used later) - Memory mem = currentProgram.getMemory(); - SymbolTable ghidraSymTbl = currentProgram.getSymbolTable(); - - // Open output file - // All symbols found (address and name) will be logged to this file - try (PrintWriter output = - new PrintWriter(new FileOutputStream(askFile("vxWorks Symbol Table Parser", - "Output file name?")))) { - - // Get address of "total number of sym tbl entries" value - Address vxNumSymEntriesAddr = - askAddress("vxWorks Symbol Table Parser", - "Address of \"total number of symbol table entries\" value?"); - int vxNumSymEntries = mem.getInt(vxNumSymEntriesAddr); - println("VxWorks symbol table has " + vxNumSymEntries + " entries"); - - // Create a GNU demangler instance - GnuDemangler demangler = new GnuDemangler(); - if (!demangler.canDemangle(currentProgram)) { - println("Unable to create demangler."); - return; - } - - // Process entries in VxWorks symbol table - Address vxSymTbl = vxNumSymEntriesAddr.subtract(vxNumSymEntries * SYM_ENTRY_SIZE); - for (int i = 0; i < vxNumSymEntries; i++) { - - if (monitor.isCancelled()) { - return; // check for cancel button - } - println("i=" + i); // visual counter - - // Extract symbol table entry values - Address symEntry = vxSymTbl.add(i * SYM_ENTRY_SIZE); - Address symNameAddr = toAddr(mem.getInt(symEntry.add(SYM_NAME_OFF))); - Address symLocAddr = toAddr(mem.getInt(symEntry.add(SYM_LOC_OFF))); - byte symType = mem.getByte(symEntry.add(SYM_TYPE_OFF)); - println("symNameAddr: 0x" + symNameAddr.toString() + ", symLocAddr: 0x" + - symLocAddr.toString() + ", symType: " + symType); - - // Remove any data or instructions that overlap this symName - // (May happen if disassembly creates invalid references) - Address a; - String symName; - for (a = symNameAddr; mem.getByte(a) != 0; a = a.add(1)) { - if (getDataAt(a) != null) { - removeDataAt(a); - } - if (getInstructionAt(a) != null) { - removeInstructionAt(a); - } - } - if (getDataAt(a) != null) { - removeDataAt(a); - } - if (getInstructionAt(a) != null) { - removeInstructionAt(a); - } - - // Turn *symNameAddr into a string and store it in symName - try { - symName = (String) createAsciiString(symNameAddr).getValue(); - } - catch (Exception e) { - println("createAsciiString: caught exception..."); - println(e.getMessage()); - return; - } - println("symName: " + symName); - - // Demangle symName - String symDemangledName = null; - try { - // if successful, symDemangledName will be non-NULL - MangledContext mangledContext = demangler.createMangledContext(symDemangledName, - null, currentProgram, symNameAddr); - symDemangledName = demangler.demangle(mangledContext).getSignature(false); - } - catch (DemangledException e) { - // if symName wasn't a mangled name, silently continue - if (!e.isInvalidMangledName()) { - println("demangle: Demangling error"); - output.println("demangle: Demangling error"); - } - } - catch (RuntimeException e) { - println("demangle: Caught runtime exception"); - output.println("demangle: Caught runtime exception"); - } - if (symDemangledName != null) { - println("symDemangledName: " + symDemangledName); - } - - // Delete any symbol in the Ghidra symbol table with the same name - SymbolIterator syms = ghidraSymTbl.getSymbols(symName); - Symbol sym; - while (syms.hasNext()) { - sym = syms.next(); - println("Deleting matching Ghidra symbol: " + sym.getName()); - ghidraSymTbl.removeSymbolSpecial(sym); - } - - // Delete any symbol in the Ghidra symbol table at the same address - if ((sym = getSymbolAt(symLocAddr)) != null) { - println("Deleting symbol at target address: " + sym.getName()); - ghidraSymTbl.removeSymbolSpecial(sym); - } - - switch (symType) { - case 0: // Undefined Symbol - println("NULL symType!"); - break; - case 2: // Local Absolute - case 3: // Global Absolute - case 6: // Local Data - case 7: // Global Data - case 8: // Local BSS - case 9: // Global BSS - // Data: log the symbol & create a Ghidra symbol at symLocAddr - output.println(symLocAddr.toString() + "\t" + symName); - createLabel(symLocAddr, symName, true); - if (symDemangledName != null) { - new DemanglerCmd(symLocAddr, symName).applyTo(currentProgram, monitor); - List symbols = - getSymbols(symName, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - ghidraSymTbl.removeSymbolSpecial(symbols.get(0)); - } - } - break; - case 4: // Local .text - case 5: // Global .text - // Code: log the symbol, disassemble, & create/name function - output.println(symLocAddr.toString() + "\t" + symName); - goTo(symLocAddr); - disassemble(symLocAddr); - createFunction(symLocAddr, symName); - if (getFunctionAt(symLocAddr) != null) { - getFunctionAt(symLocAddr).setName(symName, SourceType.USER_DEFINED); - if (symDemangledName != null) { - new DemanglerCmd(symLocAddr, symName).applyTo(currentProgram, - monitor); - List symbols = - getSymbols(symName, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - ghidraSymTbl.removeSymbolSpecial(symbols.get(0)); - } - } - } - else { - println("createFunction: Failed to create function"); - output.println("createFunction: Failed to create function"); - createLabel(symLocAddr, symName, true); - if (symDemangledName != null) { - new DemanglerCmd(symLocAddr, symName).applyTo(currentProgram, - monitor); - List symbols = - getSymbols(symName, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - ghidraSymTbl.removeSymbolSpecial(symbols.get(0)); - } - } - } - break; - default: - println("Invalid symType!"); - break; - } - - symEntry = symEntry.add(SYM_ENTRY_SIZE); // goto next entry - } - } - } -} diff --git a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_6_1.java b/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_6_1.java deleted file mode 100644 index 058e41cd29..0000000000 --- a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_6_1.java +++ /dev/null @@ -1,242 +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. - */ -// VxWorksSymTab_6_1 is an adaptation of the vxWorksSymTab script. It was modified by a customer -// to use a single loop, instead of two. It also added demangling of C++ symbol names - at least -// those that Ghidra knows how to demangle. -// -// Extracts all symbols in a VxWorks symbol table and disassembles -// the global functions. Any existing symbols in the Ghidra symbol table -// that collide with symbols defined in the VxWorks symbol table are deleted. -// -// The VxWorks symbol table is an array of symbol table entries [0..n-1] -// followed by a 32-bit value that is equal to n (number of sym tbl entries). -// Each entry in the array has the following structure: -// -// // Total size: 0x18 (24) bytes -// 0x00 int NULL -// 0x04 char *symNameAddr // symbol name -// 0x08 void *symLocAddr // location of object named by symbol -// 0x0c int NULL -// 0x10 int NULL -// 0x14 uchar symType // see switch statement below -// 0x15 uchar fill[3] -// -// The script requests: -// - Output file name: Each symbol name and address is recorded here. -// (Errors are also logged to this file.) -// - Address of "number of symbols" value: At the end of the symbol table, -// its length is recorded as a 32-bit integer. The -// script needs the address of that value to calculate -// the symbol table's start address. -// -// @category Customer Submission.vxWorks - -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.List; - -import ghidra.app.cmd.label.DemanglerCmd; -import ghidra.app.script.GhidraScript; -import ghidra.app.util.demangler.DemangledException; -import ghidra.app.util.demangler.MangledContext; -import ghidra.app.util.demangler.gnu.GnuDemangler; -import ghidra.program.model.address.Address; -import ghidra.program.model.mem.Memory; -import ghidra.program.model.symbol.*; - -public class VxWorksSymTab_6_1 extends GhidraScript { - - static final int SYM_ENTRY_SIZE = 24; - static final int SYM_NAME_OFF = 4; - static final int SYM_LOC_OFF = 8; - static final int SYM_TYPE_OFF = 0x14; - - @Override - public void run() throws Exception { - - // Get Memory and SymbolTable objects (used later) - Memory mem = currentProgram.getMemory(); - SymbolTable ghidraSymTbl = currentProgram.getSymbolTable(); - - // Open output file - // All symbols found (address and name) will be logged to this file - try (PrintWriter output = new PrintWriter( - new FileOutputStream(askFile("vxWorks Symbol Table Parser", "Output file name?")))) { - - // Get address of "total number of sym tbl entries" value - Address vxNumSymEntriesAddr = askAddress("vxWorks Symbol Table Parser", - "Address of \"total number of symbol table entries\" value?"); - int vxNumSymEntries = mem.getInt(vxNumSymEntriesAddr); - println("VxWorks symbol table has " + vxNumSymEntries + " entries"); - - // Create a GNU demangler instance - GnuDemangler demangler = new GnuDemangler(); - if (!demangler.canDemangle(currentProgram)) { - println("Unable to create demangler."); - return; - } - - // Process entries in VxWorks symbol table - Address vxSymTbl = vxNumSymEntriesAddr.subtract(vxNumSymEntries * SYM_ENTRY_SIZE); - for (int i = 0; i < vxNumSymEntries; i++) { - - if (monitor.isCancelled()) { - return; // check for cancel button - } - println("i=" + i); // visual counter - - // Extract symbol table entry values - Address symEntry = vxSymTbl.add(i * SYM_ENTRY_SIZE); - Address symNameAddr = toAddr(mem.getInt(symEntry.add(SYM_NAME_OFF))); - Address symLocAddr = toAddr(mem.getInt(symEntry.add(SYM_LOC_OFF))); - byte symType = mem.getByte(symEntry.add(SYM_TYPE_OFF)); - println("symNameAddr: 0x" + symNameAddr.toString() + ", symLocAddr: 0x" + - symLocAddr.toString() + ", symType: " + symType); - - // Remove any data or instructions that overlap this symName - // (May happen if disassembly creates invalid references) - Address a; - String symName; - for (a = symNameAddr; mem.getByte(a) != 0; a = a.add(1)) { - if (getDataAt(a) != null) { - removeDataAt(a); - } - if (getInstructionAt(a) != null) { - removeInstructionAt(a); - } - } - if (getDataAt(a) != null) { - removeDataAt(a); - } - if (getInstructionAt(a) != null) { - removeInstructionAt(a); - } - - // Turn *symNameAddr into a string and store it in symName - try { - symName = (String) createAsciiString(symNameAddr).getValue(); - } - catch (Exception e) { - println("createAsciiString: caught exception..."); - println(e.getMessage()); - return; - } - println("symName: " + symName); - - // Demangle symName - String symDemangledName = null; - try { - // if successful, symDemangledName will be non-NULL - MangledContext mangledContext = demangler.createMangledContext(symDemangledName, - null, currentProgram, symNameAddr); - symDemangledName = demangler.demangle(mangledContext).getSignature(false); - } - catch (DemangledException e) { - // if symName wasn't a mangled name, silently continue - if (!e.isInvalidMangledName()) { - println("demangle: Demangling error"); - output.println("demangle: Demangling error"); - } - } - catch (RuntimeException e) { - println("demangle: Caught runtime exception"); - output.println("demangle: Caught runtime exception"); - } - if (symDemangledName != null) { - println("symDemangledName: " + symDemangledName); - } - - // Delete any symbol in the Ghidra symbol table with the same name - SymbolIterator syms = ghidraSymTbl.getSymbols(symName); - Symbol sym; - while (syms.hasNext()) { - sym = syms.next(); - println("Deleting matching Ghidra symbol: " + sym.getName()); - ghidraSymTbl.removeSymbolSpecial(sym); - } - - // Delete any symbol in the Ghidra symbol table at the same address - if ((sym = getSymbolAt(symLocAddr)) != null) { - println("Deleting symbol at target address: " + sym.getName()); - ghidraSymTbl.removeSymbolSpecial(sym); - } - - switch (symType) { - case 0: // Undefined Symbol - println("NULL symType!"); - break; - case 2: // Local Absolute - case 3: // Global Absolute - case 6: // Local Data - case 7: // Global Data - case 8: // Local BSS - case 9: // Global BSS - // Data: log the symbol & create a Ghidra symbol at symLocAddr - output.println(symLocAddr.toString() + "\t" + symName); - createLabel(symLocAddr, symName, true); - if (symDemangledName != null) { - new DemanglerCmd(symLocAddr, symName).applyTo(currentProgram, monitor); - List symbols = - getSymbols(symName, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - ghidraSymTbl.removeSymbolSpecial(symbols.get(0)); - } - } - break; - case 4: // Local .text - case 5: // Global .text - // Code: log the symbol, disassemble, & create/name function - output.println(symLocAddr.toString() + "\t" + symName); - goTo(symLocAddr); - disassemble(symLocAddr); - createFunction(symLocAddr, symName); - if (getFunctionAt(symLocAddr) != null) { - getFunctionAt(symLocAddr).setName(symName, SourceType.USER_DEFINED); - if (symDemangledName != null) { - new DemanglerCmd(symLocAddr, symName).applyTo(currentProgram, - monitor); - List symbols = - getSymbols(symName, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - ghidraSymTbl.removeSymbolSpecial(symbols.get(0)); - } - } - } - else { - println("createFunction: Failed to create function"); - output.println("createFunction: Failed to create function"); - createLabel(symLocAddr, symName, true); - if (symDemangledName != null) { - new DemanglerCmd(symLocAddr, symName).applyTo(currentProgram, - monitor); - List symbols = - getSymbols(symName, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - ghidraSymTbl.removeSymbolSpecial(symbols.get(0)); - } - } - } - break; - default: - println("Invalid symType!"); - break; - } - - symEntry = symEntry.add(SYM_ENTRY_SIZE); // goto next entry - } - } - } -} diff --git a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java b/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java index e13b059914..0ffcf0206e 100644 --- a/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java +++ b/Ghidra/Features/GnuDemangler/ghidra_scripts/VxWorksSymTab_Finder.java @@ -39,24 +39,25 @@ // - Modify getVxSymbolClass() to recognize your program's VxWorks // symbol table entry structure, if necessary // -// @category Customer Submission.vxWorks +// @category VxWorks import java.util.List; +import ghidra.app.cmd.data.CreateDataCmd; import ghidra.app.cmd.disassemble.DisassembleCommand; import ghidra.app.cmd.label.DemanglerCmd; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.script.GhidraScript; -import ghidra.app.util.demangler.DemangledException; -import ghidra.app.util.demangler.MangledContext; +import ghidra.app.util.PseudoDisassembler; +import ghidra.app.util.demangler.*; import ghidra.app.util.demangler.gnu.GnuDemangler; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; import ghidra.program.model.data.*; -import ghidra.program.model.listing.Data; -import ghidra.program.model.listing.Instruction; +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 { @@ -86,7 +87,8 @@ public class VxWorksSymTab_Finder extends GhidraScript { private int getFieldOffset(StructureDataType dataType, String name) { for (DataTypeComponent comp : dataType.getComponents()) { - if (comp.getFieldName().equals(name)) { + String fieldName = comp.getFieldName(); + if (name.equals(fieldName)) { return comp.getOffset(); } } @@ -430,9 +432,14 @@ public class VxWorksSymTab_Finder extends GhidraScript { // return false; //} - // symType field must be recognized type code (this test is weak) + // symType field must be recognized type code byte symType = getByte(entry.add(vxSymbol.typeOffset())); - if (!isValidSymType(symType)) { + byte zeroByte = 0; + if (vxSymbol.typeOffset+1 <= vxSymbol.length()) { + // type is always at end of symbol entry, if padded make sure is zero + zeroByte = getByte(entry.add(vxSymbol.typeOffset()+1)); + } + if (!isValidSymType(symType) || zeroByte != 0) { if (debug) { println("5: " + entry + " --> " + symType); } @@ -454,50 +461,20 @@ public class VxWorksSymTab_Finder extends GhidraScript { case 9: // Global BSS case 4: // Local .text case 5: // Global .text - case 0x11: // External ref + case 0x10: // Local BSS 6.8 + case 0x11: // Global BSS 6.8 + case 0x12: // Local Common + case 0x13: // Global Common + case 0x20: // Local Common 6.8 + case 0x21: // Global Common 6.8 + case 0x40: // Local Symbols 6.8 + case 0x41: // Global Symbols 6.8 return true; default: return false; } } - //------------------------------------------------------------------------ - // isStringPointerTable - // - // Check to see if the candidate symbol table is just a string pointer - // table. - //------------------------------------------------------------------------ - private boolean isStringPointerTable(Address offset, int table_size) throws Exception { - if (debug) { - printf("Checking for string pointer table at 0x%x\n", offset.getOffset()); - } - // Skip the first offset in the table because it can be null as a symbol table - Address cursor = offset.add(4); - long end = offset.add(table_size).getOffset(); - - while (cursor.getOffset() < end) { - long value = getInt(cursor) & 0xffffffffL; - if (isAddress(value)) { - if (!isValidSymbolString(toAddr(value))) { - if (debug) { - printf("Found non-string pointer in table at 0x%x (0x%x)\n", - cursor.getOffset(), value); - } - return false; - } - cursor = cursor.add(4); - } - else { - if (debug) { - printf("Found non-address in table at 0x%x", cursor.getOffset()); - } - return false; - } - } - - return true; - } - //------------------------------------------------------------------------ // findSymTbl // @@ -556,25 +533,13 @@ public class VxWorksSymTab_Finder extends GhidraScript { } if (i == testLen) { // May have symbol table -- verify length - int table_size = vxSymbol.length() * i; - - if (!isStringPointerTable(cursor, table_size)) { - if (getSymTblLen(cursor, vxSymbol) != 0) { - printf("\n"); - System.out.flush(); - return cursor; // found table -- stop searching - } - if (debug) { - printf("Possible symbol table at " + cursor + " has length error\n"); - } + if (getSymTblLen(cursor, vxSymbol) != 0) { + printf("\n"); + System.out.flush(); + return cursor; // found table -- stop searching } - else { - if (debug) { - printf("False-positive: String pointer table at %s, skipping\n", - cursor.toString()); - } - cursor = cursor.add(table_size); - continue; + if (debug) { + printf("Possible symbol table at " + cursor + " has length error\n"); } } @@ -659,7 +624,12 @@ public class VxWorksSymTab_Finder extends GhidraScript { if (symTblLenPtr != null) { removeConflictingSymbols("vxSymTblLen", symTblLenPtr); createLabel(symTblLenPtr, "vxSymTblLen", true); - createDWord(symTblLenPtr); + + CreateDataCmd dtCmd = new CreateDataCmd(symTblLenPtr, false, DWordDataType.dataType); + boolean created = dtCmd.applyTo(currentProgram); + if (!created) { + println("Warning: Symbol Table size could not be created"); + } } else { println("Warning: Symbol Table Size not found before of after table"); @@ -695,12 +665,11 @@ public class VxWorksSymTab_Finder extends GhidraScript { private void applyDemangled(Address addr, String mangled, String demangled) { if (demangled != null) { - new DemanglerCmd(addr, mangled).applyTo(currentProgram, monitor); - List symbols = - getSymbols(mangled, currentProgram.getGlobalNamespace()); - if (!symbols.isEmpty()) { - currentProgram.getSymbolTable().removeSymbolSpecial(symbols.get(0)); - } + DemanglerOptions options = new DemanglerOptions(); + options.setApplySignature(true); + options.setApplyCallingConvention(true); + options.setDemangleOnlyKnownPatterns(false); + new DemanglerCmd(addr, mangled, options).applyTo(currentProgram, monitor); } return; @@ -714,11 +683,22 @@ public class VxWorksSymTab_Finder extends GhidraScript { // allows auto-analysis to operate with more information (and code/data // that isn't rapidly changing). //------------------------------------------------------------------------ - private void doLocalDisassemble(Address addr) { + private boolean doLocalDisassemble(Address addr) { // Only disassemble in memory blocks marked executable if (!isExecute(addr)) { - return; + return false; + } + + PseudoDisassembler pdis = new PseudoDisassembler(currentProgram); + pdis.setMaxInstructions(20); + if (!pdis.checkValidSubroutine(addr, true, false, true)) { + return false; + } + + // must be at least 2 contiguous instructions + if (pdis.getLastCheckValidInstructionCount()<2) { + return false; } DisassembleCommand cmd = new DisassembleCommand(addr, null, true); @@ -728,7 +708,7 @@ public class VxWorksSymTab_Finder extends GhidraScript { AddressSet set = cmd.getDisassembledAddressSet(); AutoAnalysisManager.getAnalysisManager(currentProgram).codeDefined(set); - return; + return true; } //------------------------------------------------------------------------ @@ -764,6 +744,8 @@ public class VxWorksSymTab_Finder extends GhidraScript { int symTblLen = getSymTblLen(symTbl, vxSymbol); println("Symbol table at " + symTbl + " (" + symTblLen + " entries)"); + currentProgram.getOptions(Program.PROGRAM_INFO).setString("Framework", "vxWorks"); + // Name the VxWorks symbol table removeConflictingSymbols("vxSymTbl", symTbl); createLabel(symTbl, "vxSymTbl", true); @@ -836,9 +818,6 @@ public class VxWorksSymTab_Finder extends GhidraScript { symType + ", name: " + symName); } - // Clear any conflicting symbols from the Ghidra symbol table - removeConflictingSymbols(symName, symLoc); - // If entry type is data, simply create a Ghidra symbol for it. // If entry type is code, disassemble it and create function. switch (symType) { @@ -855,27 +834,26 @@ public class VxWorksSymTab_Finder extends GhidraScript { case 9: // Global BSS case 0x11: // External ref - createLabel(symLoc, symName, true); + createLabel(symLoc, symName, true, SourceType.IMPORTED); applyDemangled(symLoc, symName, symDemangledName); break; case 4: // Local .text case 5: // Global .text - doLocalDisassemble(symLoc); - createFunction(symLoc, symName); - if (getFunctionAt(symLoc) != null) { - getFunctionAt(symLoc).setName(symName, SourceType.USER_DEFINED); - applyDemangled(symLoc, symName, symDemangledName); - } - else { - println("createFunction: Failed to create function"); - createLabel(symLoc, symName, true); - applyDemangled(symLoc, symName, symDemangledName); + createLabel(symLoc, symName, true, SourceType.IMPORTED); + boolean isCode = doLocalDisassemble(symLoc); + if (isCode) { + Function function = createFunction(symLoc, symName); + if (function == null) { + println("createFunction: Failed to create function " + symLoc); + } } + applyDemangled(symLoc, symName, symDemangledName); break; default: + createLabel(symLoc, symName, true, SourceType.IMPORTED); println("Invalid symType " + symType + " !"); break; } diff --git a/Ghidra/Features/PyGhidra/src/main/py/README.md b/Ghidra/Features/PyGhidra/src/main/py/README.md index b3bd5f82c2..9c61a8deca 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/README.md +++ b/Ghidra/Features/PyGhidra/src/main/py/README.md @@ -141,11 +141,15 @@ def program_context( ### pyghidra.analyze() ```python -def analyze(program: "Program"): +def analyze( + program: "Program", + monitor: Optional["TaskMonitor"] = None + ) -> str: """ Analyzes the given program. :param program: The Ghidra program to analyze. + :return: The analysis log. """ ``` diff --git a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py index 0bb887e6fd..2f0a4dc033 100644 --- a/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py +++ b/Ghidra/Features/PyGhidra/src/main/py/src/pyghidra/api.py @@ -162,11 +162,12 @@ def program_context( def analyze( program: "Program", monitor: Optional["TaskMonitor"] = None - ): + ) -> str: """ Analyzes the given program. :param program: The Ghidra program to analyze. + :return: The analysis log. """ from ghidra.app.script import GhidraScriptUtil from ghidra.program.util import GhidraProgramUtilities @@ -181,12 +182,16 @@ def analyze( mgr: AutoAnalysisManager = AutoAnalysisManager.getAnalysisManager(program); mgr.initializeOptions(); mgr.reAnalyzeAll(None); - analysisTool = mgr.getAnalysisTool(); - if analysisTool is None or analysisTool.threadIsBackgroundTaskThread(): - mgr.startAnalysis(monitor, True); # yields to analysis - else: - mgr.waitForAnalysis(None, monitor); # waits for all analysis to complete + mgr_log = "" + def get_log(manager, _is_cancelled): + nonlocal mgr_log + mgr_log += manager.getMessageLog().toString() + mgr.addListener(get_log) + mgr.startAnalysis(monitor, True); # yields to analysis + if not monitor.isCancelled(): + monitor.cancel() GhidraProgramUtilities.markProgramAnalyzed(program) + return mgr_log finally: GhidraScriptUtil.releaseBundleHostReference() diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java index 0f7cfaf826..cdc2aaac8d 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java @@ -111,6 +111,10 @@ public class SarifController implements ObjectSelectedListener clazz) { defaultGraphHandler = clazz; } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java index a9f5c62edb..9192e8a5d3 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java @@ -92,6 +92,31 @@ public class SarifUtils { return locations; } + public static JsonArray setLocation(Address addr, String kind, String uri, String name, String fqname, int index) { + JsonArray locations = new JsonArray(); + JsonObject element = new JsonObject(); + locations.add(element); + JsonObject ploc = new JsonObject(); + JsonArray lloc = new JsonArray(); + element.add("physicalLocation", ploc); + element.add("logicalLocations", lloc); + JsonObject artifact = new JsonObject(); + artifact.addProperty("uri", uri); + JsonObject address = new JsonObject(); + ploc.add("artifactLocation", artifact); + ploc.add("address", address); + address.addProperty("absoluteAddress", addr.getOffset()); + if (name != null) { + address.addProperty("name", name); + } + address.addProperty("kind", kind); + address.addProperty("fullyQualifiedName", fqname); + JsonObject ll = new JsonObject(); + lloc.add(ll); + ll.addProperty("index", index); + return locations; + } + @SuppressWarnings("unchecked") public static AddressSet getLocations(Map result, Program program, AddressSet set) throws AddressOverflowException { diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/export/ExtLogicalLocation.java b/Ghidra/Features/Sarif/src/main/java/sarif/export/ExtLogicalLocation.java new file mode 100644 index 0000000000..f8a43dccf6 --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/export/ExtLogicalLocation.java @@ -0,0 +1,49 @@ +/* ### + * 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 sarif.export; + +import ghidra.program.model.data.ISF.IsfObject; +import ghidra.program.model.listing.Function; + +public class ExtLogicalLocation implements IsfObject { + + String name; + String kind; + String decoratedName; + String fullyQualifiedName; + String uri; + + public ExtLogicalLocation(String key, Function function, String location, String op) { + this.name = key; + this.kind = "variable"; + this.decoratedName = op; + this.fullyQualifiedName = location + ":" + name; + this.uri = function.getProgram().getExecutablePath(); + } + + public String getName() { + return name; + } + + public String getDecoratedName() { + return decoratedName; + } + + public String getFullyQualfiedName() { + return fullyQualifiedName; + } + +} diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifObject.java b/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifObject.java index 343e54fdfb..68115caedb 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifObject.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/export/SarifObject.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. @@ -67,6 +67,13 @@ public class SarifObject implements IsfObject { } } + public SarifObject(String key, String ruleKey, ExtLogicalLocation lloc, JsonElement tree, Address addr, int index) { + this(key, ruleKey, tree); + if (addr != null) { + locations = SarifUtils.setLocation(addr, "data", lloc.uri, lloc.name, lloc.fullyQualifiedName, index); + } + } + protected void writeLocations(Address min, Address max) { if (SARIF) { locations = SarifUtils.setLocations(min, max); diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/export/WrappedLogicalLocation.java b/Ghidra/Features/Sarif/src/main/java/sarif/export/WrappedLogicalLocation.java new file mode 100644 index 0000000000..731ca3698d --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/export/WrappedLogicalLocation.java @@ -0,0 +1,47 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sarif.export; + +import ghidra.program.model.address.Address; + +public class WrappedLogicalLocation { + + private ExtLogicalLocation lloc; + private Address addr; + private int index; + + public WrappedLogicalLocation(ExtLogicalLocation lloc, Address addr) { + this.lloc = lloc; + this.addr = addr; + } + + public ExtLogicalLocation getLogicalLocation() { + return lloc; + } + + public Address getAddress() { + return addr; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + +} diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java index 3f582677c0..ed15729210 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java @@ -191,6 +191,11 @@ public interface EmuSyscallLibrary extends PcodeUseropLibrary { return false; } + @Override + public Class getOutputType() { + return void.class; + } + @Override public PcodeUseropLibrary getDefiningLibrary() { return syslib; diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index 584c09e7d6..919536d3c9 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -123,6 +123,8 @@ icon.widget.imagepanel.zoom.out = icon.zoom.out icon.widget.filterpanel.filter.off = filter_off.png icon.widget.filterpanel.filter.on = filter_on.png +icon.text.field.clear = icon.delete[size(12,12)] + icon.widget.pathmanager.reset = trash-empty.png icon.widget.table.header.help = info_small.png diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionBindingPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionBindingPanel.java index 4cc64eca82..bc83fdd188 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionBindingPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionBindingPanel.java @@ -15,12 +15,13 @@ */ package docking; -import java.awt.BorderLayout; import java.util.Objects; import javax.swing.*; -import docking.widgets.checkbox.GCheckBox; +import docking.widgets.EmptyBorderButton; +import docking.widgets.label.GLabel; +import generic.theme.GIcon; import gui.event.MouseBinding; /** @@ -31,9 +32,7 @@ public class ActionBindingPanel extends JPanel { private static final String DISABLED_HINT = "Select an action"; private KeyEntryPanel keyEntryPanel; - private JCheckBox useMouseBindingCheckBox; private MouseEntryTextField mouseEntryField; - private JPanel textFieldPanel; private DockingActionInputBindingListener listener; @@ -47,42 +46,31 @@ public class ActionBindingPanel extends JPanel { setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); - textFieldPanel = new JPanel(new BorderLayout()); - keyEntryPanel = new KeyEntryPanel(20, ks -> listener.keyStrokeChanged(ks)); keyEntryPanel.setDisabledHint(DISABLED_HINT); keyEntryPanel.setEnabled(false); // enabled on action selection + mouseEntryField = new MouseEntryTextField(20, mb -> listener.mouseBindingChanged(mb)); mouseEntryField.setDisabledHint(DISABLED_HINT); - mouseEntryField.setEnabled(false); // enabled on action selection - textFieldPanel.add(keyEntryPanel, BorderLayout.NORTH); + JButton clearMouseButton = new EmptyBorderButton(new GIcon("icon.text.field.clear")); + clearMouseButton.setName("Clear Mouse Binding"); + clearMouseButton.addActionListener(e -> mouseEntryField.clearMouseBinding()); - String checkBoxText = "Enter Mouse Binding"; - useMouseBindingCheckBox = new GCheckBox(checkBoxText); - useMouseBindingCheckBox - .setToolTipText("When checked, the text field accepts mouse buttons"); - useMouseBindingCheckBox.setName(checkBoxText); - useMouseBindingCheckBox.addItemListener(e -> updateTextField()); + GLabel keyBindingLabel = new GLabel("Key Binding: "); + JTextField tf = keyEntryPanel.getTextField(); + keyBindingLabel.setLabelFor(tf); - add(textFieldPanel); - add(Box.createHorizontalStrut(5)); - add(useMouseBindingCheckBox); - } + GLabel mouseBindingLabel = new GLabel("Mouse Binding: "); + mouseBindingLabel.setLabelFor(mouseBindingLabel); - private void updateTextField() { - - if (useMouseBindingCheckBox.isSelected()) { - textFieldPanel.remove(keyEntryPanel); - textFieldPanel.add(mouseEntryField, BorderLayout.NORTH); - } - else { - textFieldPanel.remove(mouseEntryField); - textFieldPanel.add(keyEntryPanel, BorderLayout.NORTH); - } - - validate(); - repaint(); + add(keyBindingLabel); + add(keyEntryPanel); + add(Box.createHorizontalStrut(30)); + add(mouseBindingLabel); + add(mouseEntryField); + add(Box.createHorizontalStrut(2)); + add(clearMouseButton); } public void setKeyBindingData(KeyStroke ks, MouseBinding mb) { @@ -113,11 +101,6 @@ public class ActionBindingPanel extends JPanel { } public void clearMouseBinding() { - mouseEntryField.clearField(); + mouseEntryField.clearMouseBinding(); } - - public boolean isMouseBinding() { - return useMouseBindingCheckBox.isSelected(); - } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryPanel.java index 6fac267970..c265b5b891 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryPanel.java @@ -18,7 +18,7 @@ package docking; import javax.swing.*; import docking.widgets.EmptyBorderButton; -import resources.Icons; +import generic.theme.GIcon; /** * A panel that holds a {@link KeyEntryTextField} and a button for clearing the current key binding. @@ -41,7 +41,7 @@ public class KeyEntryPanel extends JPanel { setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); keyEntryField = new KeyEntryTextField(columns, listener); - clearButton = new EmptyBorderButton(Icons.DELETE_ICON); + clearButton = new EmptyBorderButton(new GIcon("icon.text.field.clear")); clearButton.setName("Clear Key Binding"); clearButton.addActionListener(e -> keyEntryField.clearKeyStroke()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java index dad11ec184..a37f025dc8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java @@ -15,8 +15,8 @@ */ package docking; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; +import java.awt.event.*; +import java.awt.event.FocusEvent.Cause; import java.util.Objects; import javax.swing.KeyStroke; @@ -47,7 +47,21 @@ public class KeyEntryTextField extends HintTextField { getAccessibleContext().setAccessibleName(getName()); setColumns(columns); this.listener = listener; + + // remove the default mouse listeners to prevent pasting + MouseListener[] oldListeners1 = getMouseListeners(); + for (MouseListener l : oldListeners1) { + removeMouseListener(l); + } + addKeyListener(new MyKeyListener()); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + requestFocusInWindow(Cause.MOUSE_EVENT); + } + }); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/MouseEntryTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/MouseEntryTextField.java index 5ee2e14722..31d0536911 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/MouseEntryTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/MouseEntryTextField.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. @@ -16,6 +16,7 @@ package docking; import java.awt.event.*; +import java.awt.event.FocusEvent.Cause; import java.util.Objects; import java.util.function.Consumer; @@ -37,6 +38,12 @@ public class MouseEntryTextField extends HintTextField { getAccessibleContext().setAccessibleName(getName()); this.listener = Objects.requireNonNull(listener); + // remove the default mouse listeners to prevent pasting + MouseListener[] oldListeners1 = getMouseListeners(); + for (MouseListener l : oldListeners1) { + removeMouseListener(l); + } + addMouseListener(new MyMouseListener()); addKeyListener(new MyKeyListener()); } @@ -63,10 +70,23 @@ public class MouseEntryTextField extends HintTextField { processMouseBinding(mb, false); } + /** + * Clears the state of this class, but does not notify listeners. This allows clients to + * control the state of the field without having a callback change the client state. + */ public void clearField() { processMouseBinding(null, false); } + /** + * Clears the state of this class and notifies this client. This effectively allows for the + * programmatic setting of the mouse binding in use to be null, or in the 'no mouse binding set' + * state. + */ + public void clearMouseBinding() { + processMouseBinding(null, true); + } + private void processMouseBinding(MouseBinding mb, boolean notify) { this.mouseBinding = mb; @@ -90,6 +110,8 @@ public class MouseEntryTextField extends HintTextField { return; } + requestFocusInWindow(Cause.MOUSE_EVENT); + int modifiersEx = e.getModifiersEx(); int button = e.getButton(); @@ -102,6 +124,7 @@ public class MouseEntryTextField extends HintTextField { } processMouseBinding(new MouseBinding(button, modifiersEx), true); + e.consume(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java index 50662b872d..06a1626658 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.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,7 +15,6 @@ */ package docking.action; -import java.awt.event.InputEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.*; @@ -103,9 +102,6 @@ public class KeyBindingsManager implements PropertyChangeListener { // map standard keystroke to action doAddKeyBinding(provider, action, keyStroke); - - // map workaround keystroke to action - fixupAltGraphKeyStrokeMapping(provider, action, keyStroke); } public String validateActionKeyBinding(DockingActionIf dockingAction, KeyStroke ks) { @@ -145,24 +141,6 @@ public class KeyBindingsManager implements PropertyChangeListener { return null; } - private void fixupAltGraphKeyStrokeMapping(ComponentProvider provider, DockingActionIf action, - KeyStroke keyStroke) { - - // special case - int modifiers = keyStroke.getModifiers(); - if ((modifiers & InputEvent.ALT_DOWN_MASK) == InputEvent.ALT_DOWN_MASK) { - // - // Also register the 'Alt' binding with the 'Alt Graph' mask. This fixes the but - // on Windows (https://bugs.openjdk.java.net/browse/JDK-8194873) - // that have different key codes for the left and right Alt keys. - // - modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK; - KeyStroke updateKeyStroke = - KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, false); - doAddKeyBinding(provider, action, updateKeyStroke, keyStroke); - } - } - private void doAddKeyBinding(ComponentProvider provider, DockingActionIf action, KeyStroke keyStroke) { doAddKeyBinding(provider, action, keyStroke, keyStroke); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java index 5851a9238f..6d64c1a88a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -94,7 +94,7 @@ public class KeyBindingUtils { public static ToolOptions importKeyBindings() { // show a filechooser for the user to choose a location InputStream inputStream = getInputStreamForFile(getStartingDir()); - return createOptionsforKeybindings(inputStream); + return createOptionsforKeyBindings(inputStream); } /** @@ -107,7 +107,7 @@ public class KeyBindingUtils { * @return An options object that is composed of key binding names and their * associated keystrokes. */ - public static ToolOptions createOptionsforKeybindings(InputStream inputStream) { + public static ToolOptions createOptionsforKeyBindings(InputStream inputStream) { if (inputStream == null) { return null; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.java index 0321556538..86179c0583 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.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. @@ -54,7 +54,7 @@ public class SetKeyBindingAction extends DockingAction { if (!action.getKeyBindingType().supportsKeyBindings()) { Component parent = windowManager.getActiveComponent(); - Msg.showInfo(getClass(), parent, "Unable to Set Keybinding", + Msg.showInfo(getClass(), parent, "Unable to Set Key Binding", "Action \"" + getActionName(action) + "\" does not support key bindings"); return; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index 3ce3f3cde8..d49142f86a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.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. @@ -241,7 +241,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener { private void loadKeyBindingFromOptions(DockingActionIf action, ActionTrigger actionTrigger) { String fullName = action.getFullName(); - String description = "Keybinding for " + fullName; + String description = "Key Binding for " + fullName; options.registerOption(fullName, OptionType.ACTION_TRIGGER, actionTrigger, null, description); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java index b7deb1f04d..712f684ad0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.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 @@ import docking.action.DockingActionIf; import ghidra.docking.util.LookAndFeelUtils; import ghidra.util.StringUtilities; -class DockingToolBarUtils { +public class DockingToolBarUtils { private static final String START_KEYBINDING_TEXT = "


("; private static final String END_KEYBINDNIG_TEXT = ")
"; @@ -37,16 +37,25 @@ class DockingToolBarUtils { * @param button the button * @param action the action */ - static void setToolTipText(JButton button, DockingActionIf action) { + public static void setToolTipText(JButton button, DockingActionIf action) { + String text = createToolTipText(button, action); + button.setToolTipText(text); + } + /** + * Creates tooltip text for the given action and button. This is intended to be used for + * buttons that represent the given action. + * @param button the button that is the target for the text + * @param action the action that is the source of the button + * @return the text + */ + public static String createToolTipText(JButton button, DockingActionIf action) { String toolTipText = getToolTipText(action); String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding()); if (keyBindingText != null) { - button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText)); - } - else { - button.setToolTipText(toolTipText); + return combingToolTipTextWithKeyBinding(toolTipText, keyBindingText); } + return toolTipText; } private static String combingToolTipTextWithKeyBinding(String toolTipText, diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java index 9904fdf35e..99f266a571 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java @@ -130,7 +130,7 @@ public class EmptyBorderButton extends JButton { @Override public void setBorder(Border border) { - // To keep UI from installing a non-appropriate border (such as when switching themes), + // To keep UI from installing an incorrect border (such as when switching themes), // only allow borders created by this class to be set. if (border == RAISED_BUTTON_BORDER || border == LOWERED_BUTTON_BORDER || border == FOCUSED_BUTTON_BORDER || border == NO_BUTTON_BORDER) { @@ -171,6 +171,12 @@ public class EmptyBorderButton extends JButton { setBorder(NO_BUTTON_BORDER); } + @Override + public void setEnabled(boolean b) { + setBorder(NO_BUTTON_BORDER); + super.setEnabled(b); + } + protected void updateBorderBasedOnState() { if (!isEnabled()) { return; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/MultiLineLabel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/MultiLineLabel.java index 704ff3cca8..443eac30e0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/MultiLineLabel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/MultiLineLabel.java @@ -26,7 +26,7 @@ import utilities.util.reflection.ReflectionUtilities; /** * - * Class to render a String that has new line characters as a multiline + * Class to render a String that has new line characters as a multi-line * label. Calculates the resizing and centering characteristics. *

* Not affected by HTML formatting. @@ -49,12 +49,19 @@ public class MultiLineLabel extends JPanel { protected String[] lines; // lines to text to display protected int num_lines; // number of lines protected int margin_width; // left and right margins - protected int margin_height; // top and botton margins + protected int margin_height; // top and bottom margins protected int line_height; // total height of font protected int line_ascent; // font height above baseline protected int[] line_widths; // how wide each line is protected int max_width; // width of widest line protected int alignment = CENTER; // default alignment of text + private VerticalAlignment verticalAlignment = VerticalAlignment.MIDDLE; + + /** Values for controlling vertical alignment of the text */ + public enum VerticalAlignment { + TOP, + MIDDLE; + } /** * Default constructor. @@ -91,9 +98,9 @@ public class MultiLineLabel extends JPanel { } /** - * breaks specified label into array of lines. + * Breaks specified label into array of lines. * - *@param label String to display in canvas. + *@param label String to display. */ protected void newLabel(String label) { if (label == null) { @@ -119,9 +126,6 @@ public class MultiLineLabel extends JPanel { protected void measure() { FontMetrics fm = this.getFontMetrics(this.getFont()); - - // if no font metrics yet, just return - if (fm == null) { return; } @@ -139,9 +143,9 @@ public class MultiLineLabel extends JPanel { } /** - * Set a new label for JPanel + * Set a new label to display. * - * @param label String to display in canvas + * @param label String to display */ public void setLabel(String label) { @@ -163,7 +167,7 @@ public class MultiLineLabel extends JPanel { } /** - * Get the label text. + * {@return the label text.} */ public String getLabel() { StringBuffer sb = new StringBuffer(); @@ -189,11 +193,6 @@ public class MultiLineLabel extends JPanel { repaint(); } - /** - * Sets a new color for Canvas - * - *@param c Color to display in canvas - */ @Override public void setForeground(Color c) { super.setForeground(c); @@ -209,6 +208,15 @@ public class MultiLineLabel extends JPanel { repaint(); } + /** + * Sets the vertical alignment of the text. The default is {@link VerticalAlignment#MIDDLE}. + * @param alignment the alignment + */ + public void setVerticalAlignment(VerticalAlignment alignment) { + this.verticalAlignment = alignment; + repaint(); + } + /** * Set margin width. * @param mw the new margin width. @@ -227,29 +235,20 @@ public class MultiLineLabel extends JPanel { repaint(); } - /** - * Get alignment for text, LEFT, CENTER, RIGHT. - */ public final int getAlignment() { return alignment; } - /** - * Get margin width. - */ public final int getMarginWidth() { return margin_width; } - /** - *Get margin height. - */ public final int getMarginHeight() { return margin_height; } /** - * This method is invoked after Canvas is first created + * This method is invoked after this class is first created * but before it can be actually displayed. After we have * invoked our superclass's addNotify() method, we have font * metrics and can successfully call measure() to figure out @@ -257,48 +256,38 @@ public class MultiLineLabel extends JPanel { */ @Override public void addNotify() { - super.addNotify(); measure(); } - /** - * This method is called by a layout manager when it wants - * to know how big we'd like to be - */ @Override - public java.awt.Dimension getPreferredSize() { + public Dimension getPreferredSize() { return new Dimension(max_width + 2 * margin_width, num_lines * line_height + 2 * margin_height); } - /** - * This method is called when layout manager wants to - * know the bare minimum amount of space we need to get by. - */ @Override - public java.awt.Dimension getMinimumSize() { + public Dimension getMinimumSize() { return new Dimension(max_width, num_lines * line_height); } - /** - * This method draws label (applets use same method). - * Note that it handles the margins and the alignment, but - * that is does not have to worry about the color or font -- - * the superclass takes care of setting those in the Graphics - * object we've passed. - * @param g the graphics context to paint with. - */ @Override public void paint(Graphics g) { - int x, y; - Dimension d = this.getSize(); -// g.clearRect(0, 0, d.width, d.height); + paintBorder(g); - y = line_ascent + (d.height - num_lines * line_height) / 2; + Dimension d = this.getSize(); + + int y; + if (verticalAlignment == VerticalAlignment.MIDDLE) { + y = line_ascent + (d.height - num_lines * line_height) / 2; + } + else { + y = margin_height + line_ascent; + } + int x; for (int i = 0; i < num_lines; i++, y += line_height) { switch (alignment) { case LEFT: @@ -313,14 +302,11 @@ public class MultiLineLabel extends JPanel { break; } + GraphicsUtils.drawString(this, g, lines[i], x, y); } } - /** - * Simple test for the MultiLineLabel class. - * @param args not used - */ public static void main(String[] args) { MultiLineLabel mlab = new MultiLineLabel( diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileChooserToggleButton.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileChooserToggleButton.java index 243b6a1d34..9a213fd49f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileChooserToggleButton.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/FileChooserToggleButton.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,36 +15,45 @@ */ package docking.widgets.filechooser; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.Color; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.io.File; import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.EmptyBorder; +import javax.swing.border.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import generic.theme.GColor; import generic.theme.GThemeDefaults.Colors; public class FileChooserToggleButton extends JToggleButton { - private static final long serialVersionUID = 1L; - static final Border RAISED_BORDER = BorderFactory.createCompoundBorder( - BorderFactory.createRaisedBevelBorder(), - BorderFactory.createEmptyBorder(1, 1, 1, 1)); + // + // All border sizes are based on trial-and-error, adjusted to prevent the UI from moving as the + // user hovers and moves around with the keyboard. + // + private static final Border RAISED_BORDER = BorderFactory.createCompoundBorder( + BorderFactory.createRaisedBevelBorder(), BorderFactory.createEmptyBorder(2, 2, 2, 2)); - static final Border NO_BORDER = new EmptyBorder(RAISED_BORDER.getBorderInsets(new JButton())); + private static final Border LOWERED_BORDER = BorderFactory.createCompoundBorder( + BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(2, 2, 2, 2)); - static final Border LOWERED_BORDER = BorderFactory.createCompoundBorder( - BorderFactory.createLoweredBevelBorder(), - BorderFactory.createEmptyBorder(1, 1, 1, 1)); + // The focused border is a blue line with some padding on the outside so it is easy to see when + // the button has focus. This is similar to other buttons in the system. + private static final Color FOCUS_COLOR = new GColor("color.border.button.focused"); + private static final Border FOCUSED_BORDER = BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(1, 1, 1, 1), BorderFactory.createLineBorder(FOCUS_COLOR)); + private static final Border UNFOCUSED_BORDER = BorderFactory.createEmptyBorder(2, 2, 2, 2); - public FileChooserToggleButton(String text) { + private static final Border NO_BORDER = new EmptyBorder(4, 4, 4, 4); + + private GhidraFileChooser fileChooser; + + public FileChooserToggleButton(String text, GhidraFileChooser fileChooser) { super(text); - initBorder(); - } - - public FileChooserToggleButton(Action action) { - super(action); + this.fileChooser = fileChooser; initBorder(); } @@ -59,88 +68,92 @@ public class FileChooserToggleButton extends JToggleButton { setContentAreaFilled(false); // changes the border on hover and click - addMouseListener(new ButtonMouseListener()); + addChangeListener(new ButtonStateListener()); // works in conjunction with the mouse listener to properly set the border - addChangeListener(e -> { - if (isSelected()) { - setBorder(LOWERED_BORDER); - } - else { - setBorder(NO_BORDER); - } - }); + addChangeListener(e -> updateBorderBasedOnState()); - setFocusable(false); // this prevents the focus box from being drawn over the button + addFocusListener(new ButtonFocusListener()); + + updateBorderBasedOnState(); } - void clearBorder() { + @Override + public void setBorder(Border border) { + // To keep UI from installing an incorrect border (such as when switching themes), + // only allow borders created by this class to be set. + if (border == RAISED_BORDER || border == LOWERED_BORDER || border == NO_BORDER || + border instanceof FocusedBorder) { + super.setBorder(border); + } + } + + private void clearBorder() { setBorder(NO_BORDER); } - /** Returns the directory with which this button is associated. */ + /** {@return Returns the directory with which this button is associated.} */ File getFile() { return null; } - private class ButtonMouseListener extends MouseAdapter { - private boolean inside = false; + private void updateBorderBasedOnState() { + if (!isEnabled()) { + return; + } - private Border defaultBorder; + ButtonModel buttonModel = getModel(); + boolean pressed = buttonModel.isPressed(); + boolean rollover = buttonModel.isRollover(); + boolean armed = buttonModel.isArmed(); + boolean selected = buttonModel.isSelected(); + + Border border = NO_BORDER; + + if (selected) { + border = LOWERED_BORDER; + } + else if (pressed && (rollover || armed)) { + border = LOWERED_BORDER; + } + else if (rollover) { + border = RAISED_BORDER; + } + + border = createFocusedBorder(border, isFocusOwner()); + + setBorder(border); + } + + private Border createFocusedBorder(Border outside, boolean isFocused) { + Border inside = isFocused ? FOCUSED_BORDER : UNFOCUSED_BORDER; + return new FocusedBorder(outside, inside); + } + + private class ButtonStateListener implements ChangeListener { + @Override + public void stateChanged(ChangeEvent e) { + updateBorderBasedOnState(); + } + } + + private class ButtonFocusListener implements FocusListener { @Override - public void mouseEntered(MouseEvent me) { - if (isSelected()) { - return; - } - - defaultBorder = getBorder(); - setBorder(RAISED_BORDER); - inside = true; + public void focusGained(FocusEvent e) { + updateBorderBasedOnState(); } @Override - public void mouseExited(MouseEvent me) { - if (isSelected()) { - return; - } - - inside = false; - restoreBorder(); + public void focusLost(FocusEvent e) { + updateBorderBasedOnState(); + fileChooser.updateShortcutPanel(); } + } - @Override - public void mousePressed(MouseEvent e) { - if (isSelected()) { - return; - } - - if (e.getButton() == MouseEvent.BUTTON1) { - setBorder(LOWERED_BORDER); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (isSelected()) { - return; - } - - if (inside) { - setBorder(RAISED_BORDER); - } - else { - restoreBorder(); - } - } - - private void restoreBorder() { - if (defaultBorder != null) { - setBorder(defaultBorder); - } - else { - setBorder(NO_BORDER); - } + private class FocusedBorder extends CompoundBorder { + FocusedBorder(Border outsideBorder, Border insideBorder) { + super(outsideBorder, insideBorder); } } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GFileChooserOptionsDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GFileChooserOptionsDialog.java index 1596f3b874..55eb1525a7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GFileChooserOptionsDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GFileChooserOptionsDialog.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,7 +19,6 @@ import javax.swing.*; import docking.ReusableDialogComponentProvider; import docking.widgets.checkbox.GCheckBox; -import docking.widgets.label.GLabel; import ghidra.framework.preferences.Preferences; import ghidra.util.layout.PairLayout; @@ -47,17 +46,13 @@ class GFileChooserOptionsDialog extends ReusableDialogComponentProvider { private JComponent buildComponent() { JPanel panel = new JPanel(new PairLayout()); - showDotFilesCheckBox = new GCheckBox(); + showDotFilesCheckBox = new GCheckBox("Show '.' files"); + showDotFilesCheckBox.setToolTipText("When toggled on the file chooser will show files " + + "with names that begin with a '.' character"); showDotFilesCheckBox.getAccessibleContext().setAccessibleName("Show Dot Files"); showDotFilesCheckBox.setSelected(true); - JLabel label = new GLabel("Show '.' files"); - label.getAccessibleContext().setAccessibleName("Show Files"); - label.setToolTipText("When toggled on the file chooser will show files " + - "with names that begin with a '.' character"); - panel.add(showDotFilesCheckBox); - panel.add(label); panel.getAccessibleContext().setAccessibleName("GFile Chooser Options"); return panel; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java index 9c260e100e..ce1b919f27 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java @@ -17,6 +17,8 @@ package docking.widgets.filechooser; import java.awt.*; import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileFilter; import java.util.*; @@ -35,7 +37,11 @@ import javax.swing.text.DefaultFormatterFactory; import org.apache.commons.lang3.StringUtils; import docking.*; +import docking.action.DockingAction; +import docking.action.DockingActionIf; +import docking.action.builder.ActionBuilder; import docking.actions.KeyBindingUtils; +import docking.menu.DockingToolBarUtils; import docking.widgets.*; import docking.widgets.combobox.GComboBox; import docking.widgets.label.GDLabel; @@ -166,13 +172,19 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement private Component parent; private JPanel waitPanel; - private EmptyBorderButton backButton; - private EmptyBorderButton forwardButton; - private EmptyBorderButton upLevelButton; - private EmptyBorderButton newFolderButton; - private EmptyBorderButton refreshButton; + private JButton backButton; + private JButton forwardButton; + private JButton upButton; + private JButton newFolderButton; + private JButton refreshButton; private EmptyBorderToggleButton detailsButton; + private DockingAction upAction; + private DockingAction backAction; + private DockingAction forwardAction; + private KeyBindingChangeListener keyBindingChangeListener = new KeyBindingChangeListener(); + + private JPanel shortcutPanel; private UnselectableButtonGroup shortCutButtonGroup; private FileChooserToggleButton myComputerButton; private FileChooserToggleButton desktopButton; @@ -261,12 +273,43 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement setPreferredSize(800, 600); updateDirOnly(newModel.getHomeDirectory(), true); + + createActions(); } //================================================================================================== // Setup Methods //================================================================================================== + private void createActions() { + + String owner = getClass().getSimpleName(); + upAction = new ActionBuilder("Up One Level", owner) + .keyBinding("Alt Up") + .onAction(c -> goUp()) + .build(); + + backAction = new ActionBuilder("Last Folder Visited", owner) + .keyBinding("Alt Left") + .onAction(c -> goBack()) + .build(); + + forwardAction = new ActionBuilder("Previous Folder Visited", owner) + .keyBinding("Alt Right") + .onAction(c -> goForward()) + .build(); + + upAction.addPropertyChangeListener(keyBindingChangeListener); + backAction.addPropertyChangeListener(keyBindingChangeListener); + forwardAction.addPropertyChangeListener(keyBindingChangeListener); + + addAction(upAction); + addAction(backAction); + addAction(forwardAction); + + updateNavigationButtonToolTips(); + } + private JComponent buildWorkPanel() { buildWaitPanel(); @@ -302,7 +345,8 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement } private JPanel buildShortCutPanel() { - myComputerButton = new FileChooserToggleButton("My Computer") { + + myComputerButton = new FileChooserToggleButton("My Computer", this) { @Override File getFile() { return MY_COMPUTER; @@ -314,7 +358,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement myComputerButton.addActionListener(e -> updateMyComputer()); myComputerButton.setForeground(FOREROUND_COLOR); - desktopButton = new FileChooserToggleButton("Desktop") { + desktopButton = new FileChooserToggleButton("Desktop", this) { @Override File getFile() { return fileChooserModel.getDesktopDirectory(); @@ -327,7 +371,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement desktopButton.setForeground(FOREROUND_COLOR); desktopButton.setEnabled(fileChooserModel.getDesktopDirectory() != null); - homeButton = new FileChooserToggleButton("Home") { + homeButton = new FileChooserToggleButton("Home", this) { @Override File getFile() { return fileChooserModel.getHomeDirectory(); @@ -339,7 +383,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement homeButton.addActionListener(e -> updateHome()); homeButton.setForeground(FOREROUND_COLOR); - downloadsButton = new FileChooserToggleButton("Downloads") { + downloadsButton = new FileChooserToggleButton("Downloads", this) { @Override File getFile() { return fileChooserModel.getDownloadsDirectory(); @@ -350,7 +394,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement downloadsButton.addActionListener(e -> updateDownloads()); downloadsButton.setForeground(FOREROUND_COLOR); - recentButton = new FileChooserToggleButton("Recent") { + recentButton = new FileChooserToggleButton("Recent", this) { @Override File getFile() { return RECENT; @@ -369,27 +413,30 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement shortCutButtonGroup.add(downloadsButton); shortCutButtonGroup.add(recentButton); - JPanel shortCutPanel = new JPanel(new GridLayout(0, 1)); - shortCutPanel.getAccessibleContext().setAccessibleName("Short Cut"); - DockingUtils.setTransparent(shortCutPanel); - shortCutPanel.add(myComputerButton); - shortCutPanel.add(desktopButton); - shortCutPanel.add(homeButton); - shortCutPanel.add(downloadsButton); - shortCutPanel.add(recentButton); + shortcutPanel = new JPanel(new GridLayout(0, 1)); + shortcutPanel.getAccessibleContext().setAccessibleName("Short Cut"); + DockingUtils.setTransparent(shortcutPanel); + shortcutPanel.add(myComputerButton); + shortcutPanel.add(desktopButton); + shortcutPanel.add(homeButton); + shortcutPanel.add(downloadsButton); + shortcutPanel.add(recentButton); JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createLoweredBevelBorder()); panel.setBackground(SHORTCUT_BACKGROUND_COLOR); - panel.add(shortCutPanel, BorderLayout.NORTH); + panel.add(shortcutPanel, BorderLayout.NORTH); panel.getAccessibleContext().setAccessibleName("Short Cut"); + return panel; } private JPanel buildFileNamePanel() { - JLabel filenameLabel = new GDLabel("File name:"); + JLabel filenameLabel = new GDLabel("Filename:"); + FileDropDownSelectionDataModel model = new FileDropDownSelectionDataModel(this); filenameTextField = new DropDownSelectionTextField<>(model); + filenameLabel.setLabelFor(filenameTextField); filenameTextField.setMatchingWindowHeight(200); filenameTextField.getAccessibleContext().setAccessibleName("Filename"); filenameTextField.addCellEditorListener(new CellEditorListener() { @@ -427,9 +474,10 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement JLabel filterLabel = new GLabel("Type:"); filterLabel.getAccessibleContext().setAccessibleName("Filter"); filterCombo = new GComboBox<>(); + filterLabel.setLabelFor(filterCombo); filterCombo.setRenderer(GListCellRenderer.createDefaultTextRenderer( fileFilter -> fileFilter != null ? fileFilter.getDescription() : "")); - filterCombo.getAccessibleContext().setAccessibleName("Filter"); + filterCombo.getAccessibleContext().setAccessibleName("File Type Filter"); filterModel = (DefaultComboBoxModel) filterCombo.getModel(); addFileFilter(GhidraFileFilter.ALL); filterCombo.addItemListener(e -> rescanCurrentDirectory()); @@ -444,22 +492,12 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement return filenamePanel; } - private class SelectionListener implements DropDownSelectionChoiceListener { - - @Override - public void selectionChanged(File file) { - // take the selection and close the dialog - worker.schedule(new SetSelectedFileAndAcceptSelection(file)); - } - } - private JPanel buildHeaderPanel() { JPanel headerPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; - // gbc.insets = new Insets(PAD, PAD, PAD, PAD); JButton[] navButtons = buildNavigationButtons(); for (JButton element : navButtons) { headerPanel.add(element, gbc); @@ -613,6 +651,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement } private JButton[] buildNavigationButtons() { + backButton = new EmptyBorderButton(ICON_BACK); backButton.setName("BACK_BUTTON"); backButton.setEnabled(false); @@ -625,12 +664,13 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement forwardButton.setToolTipText("Go to previous folder visited"); forwardButton.addActionListener(e -> goForward()); - upLevelButton = new EmptyBorderButton(ICON_UP); - upLevelButton.setName(UP_BUTTON_NAME); - upLevelButton.setToolTipText("Up one level"); - upLevelButton.addActionListener(e -> goUpOneDirectoryLevel()); + upButton = new EmptyBorderButton(ICON_UP); + upButton.setName(UP_BUTTON_NAME); + upButton.setEnabled(false); + upButton.setToolTipText("Up one level"); + upButton.addActionListener(e -> goUp()); - return new JButton[] { backButton, forwardButton, upLevelButton }; + return new JButton[] { backButton, forwardButton, upButton }; } private JButton[] buildNonNavigationButtons() { @@ -1382,15 +1422,17 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement } private void doSetSelectedFileAndUpdateDisplay(File file) { - if (lastInputFocus != null) { - lastInputFocus.requestFocusInWindow(); + + Component toFocus = getRestoreFocusComponent(); + if (toFocus != null) { + toFocus.requestFocusInWindow(); } if (file == null) { return; } - // SCR 4513 - exception if we don't cancel edits before changing the display + // exception if we don't cancel edits before changing the display cancelEdits(); selectedFiles.setFile(file); updateTextFieldForFile(file); @@ -1398,6 +1440,22 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement directoryModel.setSelectedFile(file); // the list or table display } + private Component getRestoreFocusComponent() { + // ensure we transfer focus to the directory or table when the view switches + if (isTableShowing()) { + if (lastInputFocus == directoryList) { + lastInputFocus = directoryTable; + } + } + else { + if (lastInputFocus == directoryTable) { + lastInputFocus = directoryList; + } + } + + return lastInputFocus; + } + private void updateTextFieldForFile(File file) { if (file == null) { return; @@ -1452,7 +1510,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement return false; } - private void goUpOneDirectoryLevel() { + private void goUp() { cancelEdits(); if (currentDirectory() == null) { @@ -1521,8 +1579,16 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement updateDirAndSelectFile(currentDir, currentSelectedFile, true, false); } - private void updateShortcutPanel() { - // make sure that if one of the shortcut buttons is selected, the directory matches that button + void updateShortcutPanel() { + + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusOwner = kfm.getFocusOwner(); + if (focusOwner != null && !SwingUtilities.isDescendingFrom(focusOwner, shortcutPanel)) { + // only synchronize the button state if the user is not interacting with the buttons + return; + } + + // make sure the selected button matches the current directory File currentDirectory = currentDirectory(); checkShortCutButton(myComputerButton, currentDirectory); checkShortCutButton(homeButton, currentDirectory); @@ -1556,13 +1622,24 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement history.clear(); } + private void updateNavigationButtonToolTips() { + String tip = DockingToolBarUtils.createToolTipText(backButton, backAction); + backButton.setToolTipText(tip); + + tip = DockingToolBarUtils.createToolTipText(forwardButton, forwardAction); + forwardButton.setToolTipText(tip); + + tip = DockingToolBarUtils.createToolTipText(upButton, upAction); + upButton.setToolTipText(tip); + } + private void updateNavigationButtons() { backButton.setEnabled(history.hasPrevious()); forwardButton.setEnabled(history.hasNext()); File dir = currentDirectory(); boolean enable = dir != null && dir.getParentFile() != null; - upLevelButton.setEnabled(enable); + upButton.setEnabled(enable); } private void updateHistoryWithSelectedFiles(HistoryEntry historyEntry) { @@ -2235,7 +2312,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement public void runSwing() { setDirectoryList(myComputerFile, roots); setWaitPanelVisible(false); - Swing.runLater(() -> doSetSelectedFileAndUpdateDisplay(null)); + setSelectedFileAndUpdateDisplay(null); } } @@ -2254,6 +2331,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement setCurrentDirectoryDisplay(recentFile, addToHistory); List list = CollectionUtils.asList(recentList, File.class); setDirectoryList(recentFile, list); + setSelectedFileAndUpdateDisplay(null); } } @@ -2430,4 +2508,26 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement return parentDir.getName() + selectedFilesText; } } + + private class SelectionListener implements DropDownSelectionChoiceListener { + + @Override + public void selectionChanged(File file) { + // take the selection and close the dialog + worker.schedule(new SetSelectedFileAndAcceptSelection(file)); + } + } + + private class KeyBindingChangeListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent e) { + String name = e.getPropertyName(); + if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) { + updateNavigationButtonToolTips(); + } + } + + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java index b18f9f92b5..bc1d7894d2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/ClearFilterLabel.java @@ -29,9 +29,8 @@ import org.jdesktop.animation.timing.interpolation.PropertySetter; import docking.util.AnimationUtils; import docking.widgets.label.GIconLabel; +import generic.theme.GIcon; import ghidra.util.SystemUtilities; -import resources.Icons; -import resources.ResourceManager; /** * A label that displays an icon that, when clicked, will clear the contents of the @@ -39,8 +38,7 @@ import resources.ResourceManager; */ public class ClearFilterLabel extends GIconLabel { - private Icon RAW_ICON = Icons.DELETE_ICON; - private Icon ICON = ResourceManager.getScaledIcon(RAW_ICON, 10, 10); + private Icon ICON = new GIcon("icon.text.field.clear"); private static final float FULLY_TRANSPARENT = 0F; private static final float FULLY_OPAQUE = .6F; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java index 61756b8eff..6e2785b40c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java @@ -203,7 +203,7 @@ public abstract class ThreadedTableModel } private void cancelCurrentWorkerJob() { - if (worker != null && worker.isBusy()) { + if (worker != null) { worker.clearAllJobsWithInterrupt_IKnowTheRisks(); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index bd75907bfa..269ba3c924 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -145,6 +145,11 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { return false; } + @Override + public Class getOutputType() { + return void.class; + } + @Override public Method getJavaMethod() { return null; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java index aa4080681e..5f3ab37a8f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java @@ -15,14 +15,14 @@ */ package ghidra.pcode.emu.jit.analysis; -import static ghidra.pcode.emu.jit.analysis.JitVarScopeModel.*; +import static ghidra.pcode.emu.jit.analysis.JitVarScopeModel.maxAddr; +import static ghidra.pcode.emu.jit.analysis.JitVarScopeModel.overlapsLeft; import static org.objectweb.asm.Opcodes.*; import java.math.BigInteger; import java.util.*; import java.util.Map.Entry; -import org.apache.commons.collections4.iterators.ReverseListIterator; import org.objectweb.asm.*; import ghidra.app.plugin.processors.sleigh.SleighLanguage; @@ -33,6 +33,7 @@ import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.var.*; import ghidra.program.model.address.*; @@ -258,6 +259,13 @@ public class JitAllocationModel { generateLoadCode(rv); VarGen.generateValWriteCodeDirect(gen, type, vn, rv); } + + /** + * {@return the maximum address that would be occupied by the full primitive type} + */ + public Address maxPrimAddr() { + return vn.getAddress().add(type.ext().size() - 1); + } } /** @@ -296,9 +304,10 @@ public class JitAllocationModel { * @param gen the code generator * @param type the p-code type of the value expected on the JVM stack by the proceeding * bytecode + * @param ext the kind of extension to apply when adjusting from JVM size to varnode size * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv); + void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, MethodVisitor rv); /** * Emit bytecode to load the varnode's value onto the JVM stack. @@ -306,9 +315,10 @@ public class JitAllocationModel { * @param gen the code generator * @param type the p-code type of the value produced on the JVM stack by the preceding * bytecode + * @param ext the kind of extension to apply when adjusting from varnode size to JVM size * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv); + void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, MethodVisitor rv); } /** @@ -334,14 +344,16 @@ public class JitAllocationModel { } @Override - default void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + default void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { local().generateLoadCode(rv); - TypeConversions.generate(gen, this.type(), type, rv); + TypeConversions.generate(gen, this.type(), type, ext, rv); } @Override - default void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type(), rv); + default void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { + TypeConversions.generate(gen, type, this.type(), ext, rv); local().generateStoreCode(rv); } } @@ -387,9 +399,9 @@ public class JitAllocationModel { * shifted to the right to place it into position. * * @param local the local variable allocated to this part - * @param shift the number of bytes and direction to shift + * @param shift the number of bytes and direction to shift (+ is right) */ - public record MultiLocalPart(JvmLocal local, int shift) { + public record MultiLocalSub(JvmLocal local, int shift) { private JitType chooseLargerType(JitType t1, JitType t2) { return t1.size() > t2.size() ? t1 : t2; } @@ -405,15 +417,17 @@ public class JitAllocationModel { * @param gen the code generator * @param type the p-code type of the value expected on the stack by the proceeding * bytecode, which may be to load additional parts + * @param ext the kind of extension to apply when adjusting from JVM size to varnode size * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method * * @implNote We must keep temporary values in a variable of the larger of the local's or the * expected type, otherwise bits may get dropped while positioning the value. */ - public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { local.generateLoadCode(rv); JitType tempType = chooseLargerType(local.type, type); - TypeConversions.generate(gen, local.type, tempType, rv); + TypeConversions.generate(gen, local.type, tempType, ext, rv); if (shift > 0) { switch (tempType) { case IntJitType t -> { @@ -440,7 +454,7 @@ public class JitAllocationModel { default -> throw new AssertionError(); } } - TypeConversions.generate(gen, tempType, type, rv); + TypeConversions.generate(gen, tempType, type, ext, rv); } /** @@ -454,14 +468,16 @@ public class JitAllocationModel { * @param gen the code generator * @param type the p-code type of the value expected on the stack by the proceeding * bytecode, which may be to load additional parts + * @param ext the kind of extension to apply when adjusting from varnode size to JVM size * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method * * @implNote We must keep temporary values in a variable of the larger of the local's or the * expected type, otherwise bits may get dropped while positioning the value. */ - public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { JitType tempType = chooseLargerType(local.type, type); - TypeConversions.generate(gen, type, tempType, rv); + TypeConversions.generate(gen, type, tempType, ext, rv); switch (tempType) { case IntJitType t -> { if (shift > 0) { @@ -483,9 +499,9 @@ public class JitAllocationModel { rv.visitInsn(LUSHR); } } - default -> throw new AssertionError(); + default -> throw new AssertionError("tempType = " + tempType); } - TypeConversions.generate(gen, tempType, local.type, rv); + TypeConversions.generate(gen, tempType, local.type, ext, rv); switch (local.type) { case IntJitType t -> { int mask = -1 >>> (Integer.SIZE - Byte.SIZE * type.size()); @@ -524,6 +540,34 @@ public class JitAllocationModel { } } + public record MultiLocalPart(List subs, SimpleJitType type) { + public void generateLoadCode(JitCodeGenerator gen, Ext ext, MethodVisitor rv) { + subs.get(0).generateLoadCode(gen, this.type, ext, rv); + for (MultiLocalSub sub : subs.subList(1, subs.size())) { + sub.generateLoadCode(gen, this.type, ext, rv); + switch (this.type) { + case IntJitType t -> rv.visitInsn(IOR); + case LongJitType t -> rv.visitInsn(LOR); + default -> throw new AssertionError("this.type = " + this.type); + } + } + TypeConversions.generate(gen, this.type, type, ext, rv); + } + + public void generateStoreCode(JitCodeGenerator gen, Ext ext, MethodVisitor rv) { + TypeConversions.generate(gen, type, this.type, ext, rv); + for (MultiLocalSub sub : subs.subList(1, subs.size()).reversed()) { + switch (this.type) { + case IntJitType t -> rv.visitInsn(DUP); + case LongJitType t -> rv.visitInsn(DUP2); + default -> throw new AssertionError("this.type = " + this.type); + } + sub.generateStoreCode(gen, this.type, ext, rv); + } + subs.get(0).generateStoreCode(gen, this.type, ext, rv); + } + } + /** * The handler for a variable allocated in a composition of locals * @@ -537,6 +581,7 @@ public class JitAllocationModel { */ public record MultiLocalVarHandler(List parts, JitType type) implements VarHandler { + @Override public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { // Generator calls local inits directly @@ -549,31 +594,23 @@ public class JitAllocationModel { } @Override - public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { - parts.get(0).generateLoadCode(gen, this.type, rv); - for (MultiLocalPart part : parts.subList(1, parts.size())) { - part.generateLoadCode(gen, this.type, rv); - switch (this.type) { - case IntJitType t -> rv.visitInsn(IOR); - case LongJitType t -> rv.visitInsn(LOR); - default -> throw new AssertionError(); - } + public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { + for (MultiLocalPart part : parts) { + part.generateLoadCode(gen, ext, rv); + // TODO: Optimize case where last sub of cur is first sub of next } - TypeConversions.generate(gen, this.type, type, rv); + TypeConversions.generate(gen, this.type, type, ext, rv); } @Override - public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type, rv); - for (MultiLocalPart part : parts.subList(1, parts.size()).reversed()) { - switch (this.type) { - case IntJitType t -> rv.visitInsn(DUP); - case LongJitType t -> rv.visitInsn(DUP2); - default -> throw new AssertionError(); - } - part.generateStoreCode(gen, this.type, rv); + public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { + TypeConversions.generate(gen, type, this.type, ext, rv); + for (MultiLocalPart part : parts.reversed()) { + part.generateStoreCode(gen, ext, rv); + // TODO: Optimize case where last sub of cur is first sub of next } - parts.get(0).generateStoreCode(gen, this.type, rv); } } @@ -599,12 +636,14 @@ public class JitAllocationModel { } @Override - public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { throw new AssertionError(); } @Override - public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { throw new AssertionError(); } } @@ -711,6 +750,15 @@ public class JitAllocationModel { index()); } + /** + * Generate the initialization of this variable. + * + * @param mv the method visitor + * @param nameThis the name of the class defining the containing method + */ + default void generateInitCode(MethodVisitor mv, String nameThis) { + } + /** * Generate a load of this variable onto the JVM stack. * @@ -816,9 +864,7 @@ public class JitAllocationModel { } @Override - public void generateDeclCode(MethodVisitor mv, String nameThis, Label startLocals, - Label endLocals) { - super.generateDeclCode(mv, nameThis, startLocals, endLocals); + public void generateInitCode(MethodVisitor mv, String nameThis) { mv.visitLdcInsn(0); mv.visitVarInsn(ISTORE, index()); } @@ -860,6 +906,54 @@ public class JitAllocationModel { } } + public class JvmTempAlloc implements AutoCloseable { + final MethodVisitor mv; + final String prefix; + final Class primitiveType; + final int startIndex; + final int count; + final int step; + final Label start; + final Label end; + + JvmTempAlloc(MethodVisitor mv, String prefix, Class primitiveType, int count, + int startIndex, int step, Label start, Label end) { + this.mv = mv; + this.prefix = prefix; + this.primitiveType = primitiveType; + this.count = count; + this.startIndex = startIndex; + this.step = step; + this.start = start; + this.end = end; + } + + public int idx(int i) { + if (i >= count) { + throw new IndexOutOfBoundsException(i); + } + return startIndex + i * step; + } + + public void visitLocals() { + mv.visitLabel(end); + for (int i = 0; i < count; i++) { + String name = count == 1 ? prefix : (prefix + i); + mv.visitLocalVariable(name, Type.getDescriptor(primitiveType), null, start, end, + startIndex + step * i); + } + } + + public int getCount() { + return count; + } + + @Override + public void close() { + releaseTemp(this); + } + } + private final JitDataFlowModel dfm; private final JitVarScopeModel vsm; private final JitTypeModel tm; @@ -871,12 +965,13 @@ public class JitAllocationModel { private final Map handlers = new HashMap<>(); private final Map handlersPerVarnode = new HashMap<>(); private final NavigableMap locals = new TreeMap<>(); + private final Deque tempAllocs = new LinkedList<>(); /** * Construct the allocation model. * * @param context the analysis context - * @param dfm the data flow moel + * @param dfm the data flow model * @param vsm the variable scope model * @param tm the type model */ @@ -912,18 +1007,70 @@ public class JitAllocationModel { } /** - * Get the next free local index without reserving it + * Temporarily allocate the next {@code count} indices of local variables * *

- * This should be used by operator code generators after all the - * {@link JitBytesPcodeExecutorState state} bypassing local variables have been allocated. The - * variables should be scoped to that operator only, so that the ids used are freed for the next - * operator. + * These indices are reserved only within the scope of the {@code try-with-resources} block + * creating the allocation. If the {@code primitiveType} is a {@code long} or {@code double}, + * then the number of actual indices allocated is multiplied by 2, such that the total number of + * variables is given by {@code count}. + *

+ * This should be used by operator code generators after all the local variables, + * including those used to bypass {@link JitBytesPcodeExecutorState state}, have been allocated, + * or else this may generate colliding indices. These variables ought to be released before the + * next operator's code generator is invoked. + *

+ * NOTE: This will automatically invoke + * {@link MethodVisitor#visitLocalVariable(String, String, String, Label, Label, int)} and place + * the appropriate labels for you. * - * @return the next id + * @param mv the method visitor + * @param prefix the name of the local variable, or its prefix if count > 1 + * @param primitiveType the type of each variable. NOTE: If heterogeneous allocations are + * needed, invoke this method more than once in the {@code try-with-resources} + * assignment. + * @param count the number of variables to allocate + * @return the handle to the allocation. */ - public int nextFreeLocal() { - return nextLocal; + public JvmTempAlloc allocateTemp(MethodVisitor mv, String prefix, Class primitiveType, + int count) { + if (count == 0) { + return null; + } + int startIndex = nextLocal; + int step = primitiveType == long.class || primitiveType == double.class ? 2 : 1; + int countIndices = count * step; + nextLocal += countIndices; + + Label start = new Label(); + Label end = new Label(); + mv.visitLabel(start); + JvmTempAlloc temp = + new JvmTempAlloc(mv, prefix, primitiveType, count, startIndex, step, start, end); + tempAllocs.push(temp); + return temp; + } + + /** + * Temporarily allocate the next {@code count} indices of local {@code int} variables + * + * @param mv the method visitor + * @param prefix the name of the local variable, or its prefix if count > 1 + * @param count the number of variables to allocate + * @return the handle to the allocation. + * @see #allocateTemp(MethodVisitor, String, Class, int) + */ + public JvmTempAlloc allocateTemp(MethodVisitor mv, String prefix, int count) { + return allocateTemp(mv, prefix, int.class, count); + } + + private void releaseTemp(JvmTempAlloc alloc) { + JvmTempAlloc popped = tempAllocs.pop(); + if (popped != alloc) { + throw new AssertionError("Temp allocations must obey stack semantics"); + } + alloc.visitLocals(); + nextLocal = alloc.startIndex; } /** @@ -934,12 +1081,10 @@ public class JitAllocationModel { * @param desc the (whole) variable's descriptor * @return the allocated JVM locals from most to least significant */ - private List genFreeLocals(String name, List types, + private List genFreeLocals(String name, List types, VarDesc desc) { JvmLocal[] result = new JvmLocal[types.size()]; - Iterable it = language.isBigEndian() - ? types - : () -> new ReverseListIterator(types); + Iterable it = language.isBigEndian() ? types : types.reversed(); long offset = desc.offset; int i = 0; for (SimpleJitType t : it) { @@ -1022,20 +1167,55 @@ public class JitAllocationModel { * locals. */ private VarHandler createComplicatedHandler(Varnode vn) { - Entry leftEntry = locals.floorEntry(vn.getAddress()); - assert overlapsLeft(leftEntry.getValue().vn, vn); - Address min = leftEntry.getKey(); - NavigableMap sub = locals.subMap(min, true, maxAddr(vn), true); - - List parts = new ArrayList<>(); - for (JvmLocal local : sub.values()) { - int offset = (int) switch (endian) { - case BIG -> maxAddr(leftEntry.getValue().vn).subtract(maxAddr(vn)); - case LITTLE -> vn.getAddress().subtract(leftEntry.getKey()); - }; - parts.add(new MultiLocalPart(local, offset)); + JitType type = JitTypeBehavior.INTEGER.type(vn.getSize()); + NavigableMap legs = + new TreeMap<>(Comparator.comparing(Varnode::getAddress)); + switch (endian) { + case BIG -> { + Address address = vn.getAddress(); + for (SimpleJitType legType : type.legTypes()) { + Varnode legVn = new Varnode(address, legType.size()); + legs.put(legVn, new MultiLocalPart(new ArrayList<>(), legType)); + address = address.add(legType.size()); + } + } + case LITTLE -> { + Address address = maxAddr(vn); + for (SimpleJitType legType : type.legTypes()) { + address = address.subtract(legType.size() - 1); + Varnode legVn = new Varnode(address, legType.size()); + legs.put(legVn, new MultiLocalPart(new ArrayList<>(), legType)); + address = address.subtractWrap(1); + } + } } - return new MultiLocalVarHandler(parts, JitTypeBehavior.INTEGER.type(vn.getSize())); + + Entry firstEntry = locals.floorEntry(vn.getAddress()); + assert overlapsLeft(firstEntry.getValue().vn, vn); + Address min = firstEntry.getKey(); + NavigableMap sub = locals.subMap(min, true, maxAddr(vn), true); + for (JvmLocal local : sub.values()) { + Varnode startVn = legs.floorKey(local.vn); + if (startVn == null || !startVn.intersects(local.vn)) { + startVn = local.vn; + } + for (Entry ent : legs.tailMap(startVn).entrySet()) { + Varnode legVn = ent.getKey(); + if (!legVn.intersects(local.vn)) { + break; + } + int offset = (int) switch (endian) { + case BIG -> maxAddr(local.vn).subtract(maxAddr(legVn)); + case LITTLE -> legVn.getAddress().subtract(local.vn.getAddress()); + }; + ent.getValue().subs.add(new MultiLocalSub(local, offset)); + } + } + List parts = List.copyOf(legs.values()); + return new MultiLocalVarHandler(switch (endian) { + case BIG -> parts; + case LITTLE -> parts.reversed(); + }, type); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java index ab8814bd11..ef7f79d971 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java @@ -79,6 +79,10 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { return endian; } + public Varnode truncVnFromRight(Varnode vn, int amt) { + return new Varnode(vn.getAddress(), vn.getSize() - amt); + } + /** * Remove {@code amt} bytes from the right of the varnode. * @@ -94,10 +98,14 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @return the resulting value */ public JitVal truncFromRight(Varnode in1Vn, int amt, JitVal in1) { - Varnode outVn = new Varnode(in1Vn.getAddress(), in1Vn.getSize() - amt); + Varnode outVn = truncVnFromRight(in1Vn, amt); return subpiece(outVn, endian.isBigEndian() ? amt : 0, in1); } + public Varnode truncVnFromLeft(Varnode vn, int amt) { + return new Varnode(vn.getAddress().add(amt), vn.getSize() - amt); + } + /** * Remove {@code amt} bytes from the left of the varnode. * @@ -113,7 +121,7 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @return the resulting value */ public JitVal truncFromLeft(Varnode in1Vn, int amt, JitVal in1) { - Varnode outVn = new Varnode(in1Vn.getAddress().add(amt), in1Vn.getSize() - amt); + Varnode outVn = truncVnFromLeft(in1Vn, amt); return subpiece(outVn, endian.isBigEndian() ? 0 : amt, in1); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java index a69de6514a..2976a4a286 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java @@ -224,30 +224,40 @@ public class JitDataFlowState implements PcodeExecutorState { */ protected List doGetDefinitions(NavigableMap map, AddressSpace space, long offset, int size) { + long end = offset + size; List result = new ArrayList<>(); Entry preEntry = map.lowerEntry(offset); long cursor = offset; if (preEntry != null) { - if (endOf(preEntry) > offset) { + if (endOf(preEntry) > offset) { // Do I intersect the lower entry? JitVal preVal = preEntry.getValue(); Varnode preVn = new Varnode(space.getAddress(preEntry.getKey()), preVal.size()); - int shave = (int) (offset - preEntry.getKey()); - JitVal truncVal = arithmetic.truncFromLeft(preVn, shave, preVal); - cursor = endOf(preEntry); - result.add(truncVal); + int shaveLeft = (int) (offset - preEntry.getKey()); + JitVal truncVal = arithmetic.truncFromLeft(preVn, shaveLeft, preVal); + if (endOf(preEntry) > end) { // Am I contained in the lower entry? + Varnode truncVn = arithmetic.truncVnFromLeft(preVn, shaveLeft); + int shaveRight = (int) (endOf(preEntry) - end); + truncVal = arithmetic.truncFromRight(truncVn, shaveRight, truncVal); + cursor = end; + result.add(truncVal); + } + else { + cursor = endOf(preEntry); + result.add(truncVal); + } } } - long end = offset + size; for (Entry entry : map.subMap(offset, end).entrySet()) { if (entry.getKey() > cursor) { result.add(new JitMissingVar( new Varnode(space.getAddress(cursor), (int) (entry.getKey() - cursor)))); } - if (endOf(entry) > end) { + if (endOf(entry) > end) { // Do I have off the end? JitVal postVal = entry.getValue(); Varnode postVn = new Varnode(space.getAddress(entry.getKey()), postVal.size()); int shave = (int) (endOf(entry) - end); JitVal truncVal = arithmetic.truncFromRight(postVn, shave, postVal); + // NOTE: No need to check for contained here. Would have been caught above. cursor = end; result.add(truncVal); break; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java index 44f0fae094..313319f6e0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java @@ -152,17 +152,13 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { * Get the type behavior from the userop's Java method * *

- * If the userop is not backed by a Java method, or its return type is not supported, this + * If the userop is not backed by a Java method, or its output type is not supported, this * return {@link JitTypeBehavior#ANY}. * * @return the type behavior */ - private JitTypeBehavior getReturnType() { - Method method = decOp.getJavaMethod(); - if (method == null) { - return JitTypeBehavior.ANY; - } - return JitTypeBehavior.forJavaType(method.getReturnType()); + private JitTypeBehavior getOutputTypeBehavior() { + return JitTypeBehavior.forJavaType(getOutputType()); } /** @@ -210,8 +206,8 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { } else { JitOutVar out = dfm.generateOutVar(outVn); - dfm.notifyOp(new JitCallOtherDefOp(op, out, getReturnType(), decOp, inVals, inTypes, - state.captureState())); + dfm.notifyOp(new JitCallOtherDefOp(op, out, getOutputTypeBehavior(), decOp, inVals, + inTypes, state.captureState())); state.setVar(outVn, out); } } @@ -236,6 +232,11 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { return decOp.canInlinePcode(); } + @Override + public Class getOutputType() { + return decOp.getOutputType(); + } + @Override public Method getJavaMethod() { return decOp.getJavaMethod(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java index c435f48b4e..cdf7f4e8b5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java @@ -21,6 +21,8 @@ import java.util.*; import org.objectweb.asm.Opcodes; +import ghidra.lifecycle.Unfinished; + /** * The p-code type of an operand. * @@ -124,6 +126,9 @@ public interface JitType { * @return this type as an int */ SimpleJitType asInt(); + + @Override + SimpleJitType ext(); } /** @@ -201,6 +206,11 @@ public interface JitType { public IntJitType asInt() { return this; } + + @Override + public List legTypes() { + return List.of(this); + } } /** @@ -278,6 +288,11 @@ public interface JitType { public LongJitType asInt() { return this; } + + @Override + public List legTypes() { + return List.of(this); + } } /** @@ -326,6 +341,11 @@ public interface JitType { public IntJitType asInt() { return IntJitType.I4; } + + @Override + public List legTypes() { + return List.of(this); + } } /** @@ -374,10 +394,20 @@ public interface JitType { public LongJitType asInt() { return LongJitType.I8; } + + @Override + public List legTypes() { + return List.of(this); + } } /** - * WIP: The p-code types for integers of size 9 and greater. + * The p-code types for integers of size 9 and greater. + * + *

+ * We take the strategy of inlined manipulation of int locals, composed to form the full + * variable. When stored on the stack, the least-significant portion is always toward the top, + * no matter the language endianness. * * @param size the size in bytes */ @@ -432,22 +462,14 @@ public interface JitType { return size % Integer.BYTES; } - /** - * Get the p-code type that describes the part of the variable in each leg - * - *

- * Each whole leg will have the type {@link IntJitType#I4}, and the partial leg, if - * applicable, will have its appropriate smaller integer type. - * - * @return the list of types, each fitting in a JVM int. - */ - public List legTypes() { + @Override + public List legTypes() { IntJitType[] types = new IntJitType[legsAlloc()]; int i = 0; if (partialSize() != 0) { types[i++] = IntJitType.forSize(partialSize()); } - for (; i < legsWhole(); i++) { + for (; i < types.length; i++) { types[i] = IntJitType.I4; } return Arrays.asList(types); @@ -492,6 +514,11 @@ public interface JitType { public MpFloatJitType ext() { return this; } + + @Override + public List legTypes() { + return Unfinished.TODO("MpFloat"); + } } /** @@ -526,4 +553,15 @@ public interface JitType { * @return the extended type */ JitType ext(); + + /** + * Get the p-code type that describes the part of the variable in each leg + * + *

+ * Each whole leg will have the type {@link IntJitType#I4}, and the partial leg, if applicable, + * will have its appropriate smaller integer type. + * + * @return the list of types, each fitting in a JVM int, in big-endian order. + */ + List legTypes(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java index 64a152ee2a..196a67123e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java @@ -159,6 +159,9 @@ public enum JitTypeBehavior { if (cls == long.class) { return INTEGER; } + if (cls == int[].class) { + return INTEGER; + } if (cls == float.class) { return FLOAT; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java index 5bcd20b258..29254e2d70 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java @@ -222,6 +222,7 @@ public class JitTypeModel { * @param c the number of votes cast */ private void vote(JitTypeBehavior candidate, int c) { + Objects.requireNonNull(candidate); if (candidate == JitTypeBehavior.ANY || candidate == JitTypeBehavior.COPY) { return; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java index 75b5c7d8b7..9337a09f6f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java @@ -128,6 +128,11 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary { return rtOp.canInlinePcode(); } + @Override + public Class getOutputType() { + return rtOp.getOutputType(); + } + @Override public Method getJavaMethod() { return rtOp.getJavaMethod(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java index b8dbd1160c..15f679fee8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java @@ -91,7 +91,7 @@ public interface GenConsts { Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); public static final String MDESC_INTEGER__BIT_COUNT = Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_INTEGER__COMPARE_UNSIGNED = + public static final String MDESC_INTEGER__COMPARE = Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); public static final String MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS = Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE); @@ -135,6 +135,9 @@ public interface GenConsts { public static final String MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PcodeUseropDefinition.class), Type.getType(Varnode.class), Type.getType(Varnode[].class)); + public static final String MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP = + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), + Type.getType(int[].class), Type.getType(int[].class)); public static final String MDESC_JIT_COMPILED_PASSAGE__READ_INTX = Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(byte[].class), Type.INT_TYPE); public static final String MDESC_JIT_COMPILED_PASSAGE__READ_LONGX = @@ -147,6 +150,9 @@ public interface GenConsts { Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_LONG_RAW = Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); + public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_MP_INT = + Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(int[].class), + Type.getType(int[].class), Type.INT_TYPE); public static final String MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX = Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(byte[].class), Type.INT_TYPE); @@ -182,10 +188,23 @@ public interface GenConsts { Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); public static final String MDESC_$LONG_BINOP = Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); + public static final String MDESC_$SHIFT_AA = + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.INT_TYPE, + Type.getType(int[].class), Type.getType(int[].class)); + public static final String MDESC_$SHIFT_AJ = + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.INT_TYPE, + Type.getType(int[].class), Type.LONG_TYPE); + public static final String MDESC_$SHIFT_AI = + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.INT_TYPE, + Type.getType(int[].class), Type.INT_TYPE); + public static final String MDESC_$SHIFT_JA = + Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.getType(int[].class)); public static final String MDESC_$SHIFT_JJ = Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); public static final String MDESC_$SHIFT_JI = Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.INT_TYPE); + public static final String MDESC_$SHIFT_IA = + Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.getType(int[].class)); public static final String MDESC_$SHIFT_IJ = Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.LONG_TYPE); public static final String MDESC_$SHIFT_II = diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java index d09dd3cda5..4a51e8fb6f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java @@ -39,6 +39,7 @@ import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.var.ValGen; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; @@ -216,9 +217,6 @@ public class JitCodeGenerator { private final MethodVisitor initMv; private final MethodVisitor runMv; - private final Label startLocals = new Label(); - private final Label endLocals = new Label(); - /** * Construct a code generator for the given passage's target classfile * @@ -594,10 +592,11 @@ public class JitCodeGenerator { * * @param v the value to read * @param typeReq the required type of the value + * @param ext the kind of extension to apply when adjusting from JVM size to varnode size * @return the actual type of the value on the stack */ - public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq) { - return ValGen.lookup(v).generateValReadCode(this, v, typeReq, runMv); + public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq, Ext ext) { + return ValGen.lookup(v).generateValReadCode(this, v, typeReq, ext, runMv); } /** @@ -611,9 +610,10 @@ public class JitCodeGenerator { * * @param v the variable to write * @param type the actual type of the value on the stack + * @param ext the kind of extension to apply when adjusting from varnode size to JVM size */ - public void generateVarWriteCode(JitVar v, JitType type) { - VarGen.lookup(v).generateVarWriteCode(this, v, type, runMv); + public void generateVarWriteCode(JitVar v, JitType type, Ext ext) { + VarGen.lookup(v).generateVarWriteCode(this, v, type, ext, runMv); } /** @@ -858,20 +858,9 @@ public class JitCodeGenerator { */ protected void generateRunCode() { runMv.visitCode(); + final Label startLocals = new Label(); runMv.visitLabel(startLocals); - for (FixedLocal fixed : RunFixedLocal.ALL) { - fixed.generateDeclCode(runMv, nameThis, startLocals, endLocals); - } - - for (JvmLocal local : am.allLocals()) { - local.generateDeclCode(this, startLocals, endLocals, runMv); - } - // TODO: This for loop doesn't actually do anything.... - for (JitVal v : dfm.allValuesSorted()) { - VarHandler handler = am.getHandler(v); - handler.generateDeclCode(this, startLocals, endLocals, runMv); - } /** * NB. opIdx starts at 1, because JVM will ignore "Line number 0" */ @@ -886,6 +875,10 @@ public class JitCodeGenerator { } } + for (FixedLocal fixed : RunFixedLocal.ALL) { + fixed.generateInitCode(runMv, nameThis); + } + // [] RunFixedLocal.BLOCK_ID.generateLoadCode(runMv); // [blockId] @@ -929,6 +922,22 @@ public class JitCodeGenerator { for (ExceptionHandler handler : excHandlers.values()) { handler.generateRunCode(this, runMv); } + + final Label endLocals = new Label(); + runMv.visitLabel(endLocals); + + for (FixedLocal fixed : RunFixedLocal.ALL) { + fixed.generateDeclCode(runMv, nameThis, startLocals, endLocals); + } + + for (JvmLocal local : am.allLocals()) { + local.generateDeclCode(this, startLocals, endLocals, runMv); + } + // TODO: This for loop doesn't actually do anything.... + for (JitVal v : dfm.allValuesSorted()) { + VarHandler handler = am.getHandler(v); + handler.generateDeclCode(this, startLocals, endLocals, runMv); + } } /** @@ -953,8 +962,9 @@ public class JitCodeGenerator { dest.getParentFile().mkdirs(); try (OutputStream os = new FileOutputStream(dest)) { os.write(bytes); + new ProcessBuilder("javap", "-c", "-l", dest.getPath()).inheritIO().start().waitFor(); } - catch (IOException e) { + catch (IOException | InterruptedException e) { Msg.warn(this, "Could not dump class file: " + nameThis + " (" + e + ")"); } return bytes; @@ -989,7 +999,6 @@ public class JitCodeGenerator { initMv.visitMaxs(20, 20); initMv.visitEnd(); - runMv.visitLabel(endLocals); try { runMv.visitMaxs(20, 20); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java index 054bcc10ab..cc53bfb6a5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java @@ -15,11 +15,19 @@ */ package ghidra.pcode.emu.jit.gen.op; +import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP; +import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_JIT_COMPILED_PASSAGE; + import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitBinOp; /** @@ -29,6 +37,134 @@ import ghidra.pcode.emu.jit.op.JitBinOp; */ public interface BinOpGen extends OpGen { + /** + * A choice of static method parameter to take as operator output + */ + enum TakeOut { + /** + * The out (first) parameter + */ + OUT, + /** + * The left (second) parameter + */ + LEFT; + } + + /** + * Emit bytecode that implements an mp-int binary operator via delegation to a static method on + * {@link JitCompiledPassage}. The method must have the signature: + * + *
+	 * void method(int[] out, int[] inL, int[] inR);
+	 * 
+ * + *

+ * This method presumes that the left operand's legs are at the top of the stack, + * least-significant leg on top, followed by the right operand legs, also least-significant leg + * on top. This will allocate the output array, move the operands into their respective input + * arrays, invoke the method, and then place the result legs on the stack, least-significant leg + * on top. + * + * @param gen the code generator + * @param type the type of the operands + * @param methodName the name of the method in {@link JitCompiledPassage} to invoke + * @param mv the method visitor + * @param overProvisionLeft the number of extra ints to allocate for the left operand's array. + * This is to facilitate Knuth's division algorithm, which may require an extra + * leading leg in the dividend after normalization. + * @param takeOut indicates which operand of the static method to actually take for the output. + * This is to facilitate the remainder operator, because Knuth's algorithm leaves the + * remainder where there dividend was. + */ + static void generateMpDelegationToStaticMethod(JitCodeGenerator gen, MpIntJitType type, + String methodName, MethodVisitor mv, int overProvisionLeft, TakeOut takeOut) { + /** + * The strategy here will be to allocate an array for each of the operands (output and 2 + * inputs) and then invoke a static method to do the actual operation. It might be nice to + * generate inline code for small multiplications, but we're going to leave that for later. + */ + // [lleg1,...,llegN,rleg1,...,rlegN] + JitAllocationModel am = gen.getAllocationModel(); + int legCount = type.legsAlloc(); + try ( + JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", legCount); + JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", legCount)) { + // [rleg1,...,rlegN,lleg1,...,llegN] + OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); + // [lleg1,...,llegN] + OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); + // [] + + switch (takeOut) { + case OUT -> { + // [] + mv.visitLdcInsn(legCount); + // [count:INT] + mv.visitIntInsn(NEWARRAY, T_INT); + // [out:INT[count]] + mv.visitInsn(DUP); + // [out,out] + + OpGen.generateMpLegsIntoArray(tmpL, legCount + overProvisionLeft, legCount, mv); + // [inL,out,out] + OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); + // [inR,inL,out,out] + } + case LEFT -> { + // [] + mv.visitLdcInsn(legCount); + // [count:INT] + mv.visitIntInsn(NEWARRAY, T_INT); + // [out] + OpGen.generateMpLegsIntoArray(tmpL, legCount + overProvisionLeft, legCount, mv); + // [inL,out] + mv.visitInsn(DUP_X1); + // [inL,out,inL] + OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); + // [inR,inL,out,inL] + } + default -> throw new AssertionError(); + } + } + + mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName, + MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP, true); + // [out||inL:INT[count]] + + // Push the result back, in reverse order + OpGen.generateMpLegsFromArray(legCount, mv); + } + + /** + * Whether this operator is signed + *

+ * In many cases, the operator itself is not affected by the signedness of the operands; + * however, if size adjustments to the operands are needed, this can determine how those + * operands are extended. + * + * @return true for signed, false if not + */ + boolean isSigned(); + + /** + * When loading and storing variables, the kind of extension to apply + * + * @return the extension kind + */ + default Ext ext() { + return Ext.forSigned(isSigned()); + } + + /** + * When loading the right operand, the kind of extension to apply + * + * @return the extension kind + */ + default Ext rExt() { + return ext(); + } + /** * Emit code between reading the left and right operands * @@ -79,12 +215,12 @@ public interface BinOpGen extends OpGen { */ @Override default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { - JitType lType = gen.generateValReadCode(op.l(), op.lType()); + JitType lType = gen.generateValReadCode(op.l(), op.lType(), ext()); JitType rType = op.rType().resolve(gen.getTypeModel().typeOf(op.r())); lType = afterLeft(gen, op, lType, rType, rv); - JitType checkRType = gen.generateValReadCode(op.r(), op.rType()); + JitType checkRType = gen.generateValReadCode(op.r(), op.rType(), rExt()); assert checkRType == rType; JitType outType = generateBinOpRunCode(gen, op, block, lType, rType, rv); - gen.generateVarWriteCode(op.out(), outType); + gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java index 604a26ae32..056d80fa05 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java @@ -17,14 +17,16 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.*; +import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitBinOp; /** @@ -32,7 +34,11 @@ import ghidra.pcode.emu.jit.op.JitBinOp; * * @param the class of p-code op node in the use-def graph */ -public interface BitwiseBinOpGen extends BinOpGen { +public interface BitwiseBinOpGen extends IntBinOpGen { + @Override + default boolean isSigned() { + return false; + } /** * The JVM opcode to implement this operator with int operands on the stack. @@ -49,7 +55,7 @@ public interface BitwiseBinOpGen extends BinOpGen { int longOpcode(); /** - * WIP: The implementation for multi-precision ints. + * The implementation for multi-precision ints. * * @param gen the code generator * @param type the type of each operand, including the reuslt @@ -66,37 +72,25 @@ public interface BitwiseBinOpGen extends BinOpGen { */ // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("result" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) + try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { + OpGen.generateMpLegsIntoTemp(result, legCount, mv); + for (int i = 0; i < legCount; i++) { + // [lleg1,...,llegN:INT] + mv.visitVarInsn(ILOAD, result.idx(i)); + // [lleg1,...,llegN:INT,rlegN:INT] + mv.visitInsn(intOpcode()); + // [lleg1,...,olegN:INT] + mv.visitVarInsn(ISTORE, result.idx(i)); + // [lleg1,...] + } + OpGen.generateMpLegsFromTemp(result, legCount, mv); } - for (int i = 0; i < legCount; i++) { - // [lleg1,...,llegN:INT] - mv.visitVarInsn(ILOAD, firstIndex + i); - // [lleg1,...,llegN:INT,rlegN:INT] - mv.visitInsn(intOpcode()); - // [lleg1,...,olegN:INT] - mv.visitVarInsn(ISTORE, firstIndex + i); - // [lleg1,...] - } - - // Push it all back, in reverse order - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); - } - mv.visitLabel(end); } @Override default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } /** @@ -110,7 +104,7 @@ public interface BitwiseBinOpGen extends BinOpGen { @Override default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); switch (rType) { case IntJitType t -> rv.visitInsn(intOpcode()); case LongJitType t -> rv.visitInsn(longOpcode()); @@ -118,6 +112,6 @@ public interface BitwiseBinOpGen extends BinOpGen { case MpIntJitType t -> TODO("MpInt of differing sizes"); default -> throw new AssertionError(); } - return lType; + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java index d21f88bad2..68d228440c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java @@ -24,6 +24,9 @@ import ghidra.pcode.opbehavior.OpBehaviorBoolAnd; * @implNote It is the responsibility of the slaspec author to ensure boolean values are 0 or 1. * This allows us to use bitwise logic instead of having to check for any non-zero value, * just like {@link OpBehaviorBoolAnd}. Thus, this is identical to {@link IntAndOpGen}. + * @implNote Because having bits other than the least significant set in the inputs is "undefined + * behavior," we could technically optimize this by only ANDing the least significant leg + * when we're dealing with mp-ints. */ public enum BoolAndOpGen implements BitwiseBinOpGen { /** The generator singleton */ diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java index 9d0d1f6a73..c97e13507c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java @@ -17,7 +17,6 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; @@ -36,6 +35,11 @@ public enum BoolNegateOpGen implements UnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitBoolNegateOp op, JitBlock block, JitType uType, MethodVisitor rv) { @@ -48,7 +52,11 @@ public enum BoolNegateOpGen implements UnOpGen { rv.visitLdcInsn(1L); rv.visitInsn(LXOR); } - case MpIntJitType t -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> { + // Least-sig leg is on top, and it's an int. + rv.visitLdcInsn(1); + rv.visitInsn(IXOR); + } default -> throw new AssertionError(); } return uType; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java index abd1652197..6bb9f16edc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java @@ -25,6 +25,7 @@ import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.op.BranchOpGen.BranchGen; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitBranchIndOp; import ghidra.program.model.address.Address; import ghidra.program.model.lang.RegisterValue; @@ -55,9 +56,9 @@ public enum BranchIndOpGen implements OpGen { JitBlock block, MethodVisitor rv) { gen.generatePassageExit(block, () -> { // [...] - JitType targetType = gen.generateValReadCode(op.target(), op.targetType()); + JitType targetType = gen.generateValReadCode(op.target(), op.targetType(), Ext.ZERO); // [...,target:?] - TypeConversions.generateToLong(targetType, LongJitType.I8, rv); + TypeConversions.generateToLong(targetType, LongJitType.I8, Ext.ZERO, rv); // [...,target:LONG] }, ctx, rv); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java index 7813d10e41..1416e4fb81 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java @@ -27,6 +27,7 @@ import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.op.BranchOpGen.ExtBranchGen; import ghidra.pcode.emu.jit.gen.op.BranchOpGen.IntBranchGen; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.op.JitCBranchOp; @@ -146,7 +147,7 @@ public enum CBranchOpGen implements OpGen { return; } - JitType cType = gen.generateValReadCode(op.cond(), op.condType()); + JitType cType = gen.generateValReadCode(op.cond(), op.condType(), Ext.ZERO); TypeConversions.generateIntToBool(cType, rv); switch (op.branch()) { case RIntBranch ib -> IntCBranchGen.C_INT.generateCode(gen, op, ib, block, rv); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java index 6c07775ccc..994dd4c2b6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java @@ -19,23 +19,31 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.objectweb.asm.*; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; import ghidra.pcode.emu.jit.analysis.*; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; import ghidra.pcode.emu.jit.gen.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator.RetireMode; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.op.JitCallOtherDefOp; import ghidra.pcode.emu.jit.op.JitCallOtherOpIf; import ghidra.pcode.emu.jit.var.JitVal; +import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; @@ -153,6 +161,31 @@ public enum CallOtherOpGen implements OpGen { transition.generateInv(rv); } + static Parameter findOutputParameter(Parameter[] parameters, Method method) { + List found = + Stream.of(parameters).filter(p -> p.getAnnotation(OpOutput.class) != null).toList(); + return switch (found.size()) { + case 0 -> null; + case 1 -> { + Parameter p = found.get(0); + if (p.getType() == int[].class) { + yield p; + } + throw new IllegalArgumentException(""" + @%s requires parameter to have type int[] when functional=true. \ + Got %s (method %s)""".formatted( + OpOutput.class.getSimpleName(), p, method.getName())); + } + default -> { + throw new IllegalArgumentException(""" + @%s can only be applied to one parameter of method %s. \ + It is applied to: %s""".formatted( + OpOutput.class.getSimpleName(), method.getName(), + found.stream().map(Parameter::toString).collect(Collectors.joining(", ")))); + } + }; + } + /** * Emit code to implement the Direct strategy (see the class documentation) * @@ -175,6 +208,8 @@ public enum CallOtherOpGen implements OpGen { rv.visitTryCatchBlock(tryStart, tryEnd, gen.requestExceptionHandler((DecodedPcodeOp) op.op(), block).label(), NAME_THROWABLE); + JitAllocationModel am = gen.getAllocationModel(); + // [] useropField.generateLoadCode(gen, rv); // [userop] @@ -186,29 +221,106 @@ public enum CallOtherOpGen implements OpGen { rv.visitTypeInsn(CHECKCAST, owningLibName); // [library:OWNING_TYPE] Parameter[] parameters = method.getParameters(); - for (int i = 0; i < op.args().size(); i++) { - JitVal arg = op.args().get(i); - Parameter p = parameters[i]; - - JitType type = gen.generateValReadCode(arg, JitTypeBehavior.ANY); - if (p.getType() == boolean.class) { - TypeConversions.generateIntToBool(type, rv); + Parameter outputParameter = findOutputParameter(parameters, method); + if (outputParameter != null && method.getReturnType() != void.class) { + throw new IllegalArgumentException(""" + @%s cannot be applied to any parameter of a method returning non-void. \ + It's applied to %s of %s""".formatted( + OpOutput.class.getSimpleName(), outputParameter, method.getName())); + } + try (JvmTempAlloc out = + am.allocateTemp(rv, "out", int[].class, outputParameter == null ? 0 : 1)) { + MpIntJitType outMpType; + if (outputParameter != null) { + if (!(op instanceof JitCallOtherDefOp defOp)) { + outMpType = null; + rv.visitInsn(ACONST_NULL); + } + else { + outMpType = MpIntJitType.forSize(defOp.out().size()); + rv.visitLdcInsn(outMpType.legsAlloc()); + rv.visitIntInsn(NEWARRAY, T_INT); + } + rv.visitVarInsn(ASTORE, out.idx(0)); } else { - TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), rv); + outMpType = null; + } + + int argIdx = 0; + for (int i = 0; i < parameters.length; i++) { + Parameter p = parameters[i]; + + if (p == outputParameter) { + rv.visitVarInsn(ALOAD, out.idx(0)); + continue; + } + + JitVal arg = op.args().get(argIdx++); + + // TODO: Should this always be zero extension? + JitType type = gen.generateValReadCode(arg, JitTypeBehavior.ANY, Ext.ZERO); + if (p.getType() == boolean.class) { + TypeConversions.generateIntToBool(type, rv); + continue; + } + + if (p.getType() == int[].class) { + MpIntJitType mpType = MpIntJitType.forSize(type.size()); + // NOTE: Would be nice to have annotation specify signedness + TypeConversions.generate(gen, type, mpType, Ext.ZERO, rv); + int legCount = mpType.legsAlloc(); + try (JvmTempAlloc temp = am.allocateTemp(rv, "temp", legCount)) { + OpGen.generateMpLegsIntoTemp(temp, legCount, rv); + OpGen.generateMpLegsIntoArray(temp, legCount, legCount, rv); + } + continue; + } + + // Some primitive/simple type + // TODO: Should this always be zero extension? Can annotation specify? + TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), Ext.ZERO, + rv); + } + // [library,params...] + rv.visitLabel(tryStart); + rv.visitMethodInsn(INVOKEVIRTUAL, owningLibName, method.getName(), + Type.getMethodDescriptor(method), false); + // [return?] + rv.visitLabel(tryEnd); + if (outputParameter != null) { + if (outMpType != null && op instanceof JitCallOtherDefOp defOp) { + rv.visitVarInsn(ALOAD, out.idx(0)); + OpGen.generateMpLegsFromArray(outMpType.legsAlloc(), rv); + // NOTE: Want annotation to specify signedness + gen.generateVarWriteCode(defOp.out(), outMpType, Ext.ZERO); + } + // Else there's either no @OpOutput or the output operand is absent + } + else if (op instanceof JitCallOtherDefOp defOp) { + // TODO: Can annotation specify signedness of return value? + gen.generateVarWriteCode(defOp.out(), JitType.forJavaType(method.getReturnType()), + Ext.ZERO); + } + else if (method.getReturnType() != void.class) { + TypeConversions.generatePop(JitType.forJavaType(method.getReturnType()), rv); } } - // [library,params...] - rv.visitLabel(tryStart); - rv.visitMethodInsn(INVOKEVIRTUAL, owningLibName, method.getName(), - Type.getMethodDescriptor(method), false); - // [return?] - rv.visitLabel(tryEnd); - if (op instanceof JitCallOtherDefOp defOp) { - gen.generateVarWriteCode(defOp.out(), JitType.forJavaType(method.getReturnType())); + } + + static class ResourceGroup implements AutoCloseable { + private final List resources = new ArrayList<>(); + + @Override + public void close() throws Exception { + for (AutoCloseable r : resources) { + r.close(); + } } - else if (method.getReturnType() != void.class) { - TypeConversions.generatePop(JitType.forJavaType(method.getReturnType()), rv); + + public T add(T resource) { + resources.add(resource); + return resource; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java index c9dcf1d4d6..f08041e67e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java @@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatTestOp; * * @param the class of p-code op node in the use-def graph */ -public interface CompareFloatOpGen extends BinOpGen { +public interface CompareFloatOpGen extends FloatBinOpGen { /** * The JVM opcode to perform the comparison with float operands on the stack. diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java index 7fa382b629..43b8bd3cdc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java @@ -20,7 +20,7 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; @@ -34,11 +34,10 @@ import ghidra.pcode.emu.jit.op.JitIntTestOp; * * @param the class of p-code op node in the use-def graph */ -public interface CompareIntBinOpGen extends BinOpGen { +public interface CompareIntBinOpGen extends IntBinOpGen { /** - * Whether the comparison of p-code integers is signed - * + * {@inheritDoc} *

* If the comparison is unsigned, we will emit invocations of * {@link Integer#compareUnsigned(int, int)} or {@link Long#compareUnsigned(long, long)}, @@ -49,6 +48,7 @@ public interface CompareIntBinOpGen extends BinOpGen * * @return true if signed, false if not */ + @Override boolean isSigned(); /** @@ -58,6 +58,11 @@ public interface CompareIntBinOpGen extends BinOpGen */ int icmpOpcode(); + default void generateIntCmp(String methodName, MethodVisitor rv) { + rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, methodName, MDESC_INTEGER__COMPARE, + false); + } + /** * Emits bytecode for the JVM int case * @@ -69,8 +74,7 @@ public interface CompareIntBinOpGen extends BinOpGen rv.visitJumpInsn(icmpOpcode(), lblTrue); } else { - rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", - MDESC_INTEGER__COMPARE_UNSIGNED, false); + generateIntCmp("compareUnsigned", rv); rv.visitJumpInsn(ifOpcode(), lblTrue); } } @@ -94,7 +98,7 @@ public interface CompareIntBinOpGen extends BinOpGen /** * The JVM opcode to perform the conditional jump for unsigned or long integers. - * + *

* This is emitted after the application of {@link #LCMP} or the comparator method. * * @return the opcode @@ -104,7 +108,34 @@ public interface CompareIntBinOpGen extends BinOpGen @Override default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); + } + + default JitType generateMpIntCmp(JitCodeGenerator gen, MpIntJitType type, Label lblTrue, + MethodVisitor mv) { + int legCount = type.legsAlloc(); + Label lblDone = new Label(); + // Need two temps, because comparison is from *most* to least-significant + try ( + JvmTempAlloc tmpL = gen.getAllocationModel().allocateTemp(mv, "tmpL", legCount); + JvmTempAlloc tmpR = gen.getAllocationModel().allocateTemp(mv, "tmpR", legCount)) { + OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); + OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); + for (int i = 0; i < legCount; i++) { + mv.visitVarInsn(ILOAD, tmpL.idx(legCount - i - 1)); + mv.visitVarInsn(ILOAD, tmpR.idx(legCount - i - 1)); + //OpGen.generateSyserrInts(gen, 2, mv); + generateIntCmp(i == 0 ? "compare" : "compareUnsigned", mv); + if (i != legCount - 1) { + mv.visitInsn(DUP); + mv.visitJumpInsn(IFNE, lblDone); + mv.visitInsn(POP); + } + } + } + mv.visitLabel(lblDone); + mv.visitJumpInsn(ifOpcode(), lblTrue); + return IntJitType.I4; } /** @@ -123,11 +154,11 @@ public interface CompareIntBinOpGen extends BinOpGen Label lblTrue = new Label(); Label lblDone = new Label(); - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); switch (rType) { case IntJitType t -> generateIntJump(lblTrue, rv); case LongJitType t -> generateLongJump(lblTrue, rv); - case MpIntJitType t -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> generateMpIntCmp(gen, t, lblTrue, rv); default -> throw new AssertionError(); } JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java index e02aa8472c..52190562ba 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java @@ -34,6 +34,11 @@ public enum CopyOpGen implements UnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitCopyOp op, JitBlock block, JitType uType, MethodVisitor rv) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java index d34dec6cdd..b179fd80a9 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java @@ -33,7 +33,7 @@ import ghidra.pcode.emu.jit.op.JitFloatAbsOp; * This uses the unary operator generator and emits an invocation of {@link Math#abs(float)} or * {@link Math#abs(double)}, depending on the type. */ -public enum FloatAbsOpGen implements UnOpGen { +public enum FloatAbsOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java index 4c4332b202..d903f4c550 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatAddOp; * This uses the binary operator generator and simply emits {@link #FADD} or {@link #DADD} depending * on the type. */ -public enum FloatAddOpGen implements BinOpGen { +public enum FloatAddOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java new file mode 100644 index 0000000000..3e80cf42c6 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java @@ -0,0 +1,30 @@ +/* ### + * 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.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.op.JitFloatBinOp; + +/** + * An extension for floating-point binary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatBinOpGen extends BinOpGen { + @Override + default boolean isSigned() { + return false; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java index 1757504e38..526d3f9f56 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java @@ -34,7 +34,7 @@ import ghidra.pcode.emu.jit.op.JitFloatCeilOp; * This uses the unary operator generator and emits an invocation of {@link Math#ceil(double)}, * possibly surrounding it with conversions from and to float. */ -public enum FloatCeilOpGen implements UnOpGen { +public enum FloatCeilOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java index fb210ba10c..8af81ed2e2 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatDivOp; * This uses the binary operator generator and simply emits {@link #FDIV} or {@link #DDIV} depending * on the type. */ -public enum FloatDivOpGen implements BinOpGen { +public enum FloatDivOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java index f1835eb3e0..330a56bc4f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java @@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatFloat2FloatOp; *

* This uses the unary operator generator and emits {@link #F2D} or {@link #D2F}. */ -public enum FloatFloat2FloatOpGen implements UnOpGen { +public enum FloatFloat2FloatOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java index 60636d7d05..3a90bef445 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java @@ -34,7 +34,7 @@ import ghidra.pcode.emu.jit.op.JitFloatFloorOp; * This uses the unary operator generator and emits an invocation of {@link Math#floor(double)}, * possibly surrounding it with conversions from and to float. */ -public enum FloatFloorOpGen implements UnOpGen { +public enum FloatFloorOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java index dc952442ad..5de1e6215b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java @@ -36,6 +36,11 @@ public enum FloatInt2FloatOpGen implements UnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; // TODO: Is it signed? Test to figure it out. + } + private JitType gen(MethodVisitor rv, int opcode, JitType type) { rv.visitInsn(opcode); return type; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java index 651773b2a0..e202284646 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatMultOp; * This uses the binary operator generator and simply emits {@link #FMUL} or {@link #DMUL} depending * on the type. */ -public enum FloatMultOpGen implements BinOpGen { +public enum FloatMultOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java index 25822468b0..2c73c8b4b6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java @@ -33,7 +33,7 @@ import ghidra.pcode.emu.jit.op.JitFloatNaNOp; * This uses the unary operator generator and emits an invocation of {@link Float#isNaN(float)} or * {@link Double#isNaN(double)}, depending on the type. */ -public enum FloatNaNOpGen implements UnOpGen { +public enum FloatNaNOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java index 3b48f90317..e2777d0734 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java @@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatNegOp; *

* This uses the unary operator generator and emits {@link #FNEG} or {@link #DNEG}. */ -public enum FloatNegOpGen implements UnOpGen { +public enum FloatNegOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java index f7708f58b3..b28cb5d4ba 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java @@ -37,7 +37,7 @@ import ghidra.pcode.emu.jit.op.JitFloatRoundOp; * {@code round(x) = floor(x + 0.5)}. This uses the unary operator generator and emits the bytecode * to implement that definition, applying type conversions as needed. */ -public enum FloatRoundOpGen implements UnOpGen { +public enum FloatRoundOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java index a6fd21fbd3..5bb86b34fe 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java @@ -34,7 +34,7 @@ import ghidra.pcode.emu.jit.op.JitFloatSqrtOp; * This uses the unary operator generator and emits an invocation of {@link Math#sqrt(double)}, * possibly surrounding it with conversions from and to float. */ -public enum FloatSqrtOpGen implements UnOpGen { +public enum FloatSqrtOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java index 75951643f8..9bfc9a683a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatSubOp; * This uses the binary operator generator and simply emits {@link #FSUB} or {@link #DSUB} depending * on the type. */ -public enum FloatSubOpGen implements BinOpGen { +public enum FloatSubOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java index 2348237af7..e7a2f41898 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatTruncOp; * This uses the unary operator generator and emits {@link #F2I}, {@link #F2L}, {@link #D2I}, or * {@link #D2L}. */ -public enum FloatTruncOpGen implements UnOpGen { +public enum FloatTruncOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java new file mode 100644 index 0000000000..a69074fe40 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java @@ -0,0 +1,30 @@ +/* ### + * 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.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.op.JitFloatUnOp; + +/** + * An extension for floating-point unary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatUnOpGen extends UnOpGen { + @Override + default boolean isSigned() { + return false; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java index 4a210ed96d..2e9b107b5b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java @@ -15,13 +15,17 @@ */ package ghidra.pcode.emu.jit.gen.op; +import java.util.List; + import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitInt2CompOp; /** @@ -31,17 +35,69 @@ import ghidra.pcode.emu.jit.op.JitInt2CompOp; * This uses the unary operator generator and emits {@link #INEG} or {@link #LNEG}, depending on * type. */ -public enum Int2CompOpGen implements UnOpGen { +public enum Int2CompOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; // TODO: Is it? Test with 3-byte operands to figure it out. + } + + private void generateMpIntLeg2Cmp(int idx, IntJitType type, boolean givesCarry, + MethodVisitor mv) { + // [carryN-1:LONG] + mv.visitVarInsn(ILOAD, idx); + // [legN:INT,carry:LONG] + mv.visitLdcInsn(-1 >>> (Integer.SIZE - type.size() * Byte.SIZE)); + // [ff:INT,legN:INT,carry:LONG] + mv.visitInsn(IXOR); + // [invN:INT,carry:LONG] + TypeConversions.generateIntToLong(type, LongJitType.I8, Ext.ZERO, mv); + // [invN:LONG,carry:LONG] + mv.visitInsn(LADD); + // [carry|2cmpN:LONG] + if (givesCarry) { + mv.visitInsn(DUP2); + // [carry|2cmpN:LONG,carry|2cmpN:LONG] + TypeConversions.generateLongToInt(LongJitType.I8, type, Ext.ZERO, mv); + // [2cmpN:INT,carry|2cmpN:LONG] + mv.visitVarInsn(ISTORE, idx); + // [carry|2cmpN:LONG] + mv.visitLdcInsn(Integer.SIZE); + // [32:INT, carry:LONG] + mv.visitInsn(LUSHR); + // [carryN:LONG] + } + else { + TypeConversions.generateLongToInt(LongJitType.I8, type, Ext.ZERO, mv); + // [2cmpN:INT] + mv.visitVarInsn(ISTORE, idx); + // [] + } + } + + private void generateMpInt2Comp(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + int legCount = type.legsAlloc(); + try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { + OpGen.generateMpLegsIntoTemp(result, legCount, mv); + List types = type.legTypes().reversed(); + mv.visitLdcInsn(1L); // Seed the "carry in" with the 1 to add + for (int i = 0; i < legCount; i++) { + boolean isLast = i == legCount - 1; + generateMpIntLeg2Cmp(result.idx(i), types.get(i), !isLast, mv); + } + OpGen.generateMpLegsFromTemp(result, legCount, mv); + } + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitInt2CompOp op, JitBlock block, JitType uType, MethodVisitor rv) { switch (uType) { case IntJitType t -> rv.visitInsn(INEG); case LongJitType t -> rv.visitInsn(LNEG); - case MpIntJitType t -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> generateMpInt2Comp(gen, t, rv); default -> throw new AssertionError(); } return uType; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java index bb0b775129..d19eaf951c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java @@ -15,15 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.*; +import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitIntAddOp; /** @@ -36,12 +36,12 @@ import ghidra.pcode.emu.jit.op.JitIntAddOp; *

* NOTE: The multi-precision integer parts of this are a work in progress. */ -public enum IntAddOpGen implements BinOpGen { +public enum IntAddOpGen implements IntBinOpGen { /** The generator singleton */ GEN; static void generateMpIntLegAdd(JitCodeGenerator gen, int idx, boolean takesCarry, - boolean givesCarry, MethodVisitor mv) { + boolean givesCarry, boolean storesResult, MethodVisitor mv) { if (takesCarry) { // [...,llegN:INT,olegN+1:LONG] mv.visitLdcInsn(32); @@ -50,31 +50,38 @@ public enum IntAddOpGen implements BinOpGen { mv.visitInsn(DUP2_X1); mv.visitInsn(POP2); // [...,carryinN:LONG,llegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,carryinN:LONG,llegN:LONG] mv.visitInsn(LADD); // [...,sumpartN:LONG] } else { - // [...,legN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + // [...,llegN:INT] + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,sumpartN:LONG] (legN + 0) } mv.visitVarInsn(ILOAD, idx); // [...,sumpartN:LONG,rlegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,sumpartN:LONG,rlegN:LONG] mv.visitInsn(LADD); // [...,olegN:LONG] - if (givesCarry) { - mv.visitInsn(DUP2); + if (storesResult) { + if (givesCarry) { + mv.visitInsn(DUP2); + } + // [...,(olegN:LONG),olegN:LONG] + TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); + // [...,(olegN:LONG),olegN:INT] + /** NB. The store will perform the masking */ + mv.visitVarInsn(ISTORE, idx); + // [...,(olegN:LONG)] + } + else { + if (!givesCarry) { + mv.visitInsn(POP2); + } } - // [...,(olegN:LONG),olegN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); - // [...,(olegN:LONG),olegN:INT] - /** NB. The store will perform the masking */ - mv.visitVarInsn(ISTORE, idx); - // [...,(olegN:LONG)] } private void generateMpIntAdd(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { @@ -88,47 +95,40 @@ public enum IntAddOpGen implements BinOpGen { */ // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("result" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) - } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - boolean takesCarry = i != 0; // not first - generateMpIntLegAdd(gen, firstIndex + i, takesCarry, !isLast, mv); + try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { + OpGen.generateMpLegsIntoTemp(result, legCount, mv); + // [lleg1,...,llegN:INT] + for (int i = 0; i < legCount; i++) { + boolean isLast = i == legCount - 1; + boolean takesCarry = i != 0; // not first + generateMpIntLegAdd(gen, result.idx(i), takesCarry, !isLast, true, mv); + } + OpGen.generateMpLegsFromTemp(result, legCount, mv); } + } - // Push it all back, in reverse order - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); - } - mv.visitLabel(end); + @Override + public boolean isSigned() { + return false; } @Override public JitType afterLeft(JitCodeGenerator gen, JitIntAddOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntAddOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); switch (rType) { case IntJitType t -> rv.visitInsn(IADD); case LongJitType t -> rv.visitInsn(LADD); case MpIntJitType t when t.size() == lType.size() -> generateMpIntAdd(gen, t, rv); - case MpIntJitType t -> TODO("MpInt of differing sizes"); + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } - return lType; + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.java new file mode 100644 index 0000000000..bfd398b686 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.java @@ -0,0 +1,27 @@ +/* ### + * 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.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.op.JitBinOp; + +/** + * An extension for integer binary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntBinOpGen extends BinOpGen { + // Intentionally empty +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java index 395b3d54b9..8c23201f47 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java @@ -18,13 +18,15 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.*; +import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitIntCarryOp; /** @@ -48,7 +50,7 @@ import ghidra.pcode.emu.jit.op.JitIntCarryOp; *

* NOTE: The multi-precision integer parts of this are a work in progress. */ -public enum IntCarryOpGen implements BinOpGen { +public enum IntCarryOpGen implements IntBinOpGen { /** The generator singleton */ GEN; @@ -59,35 +61,36 @@ public enum IntCarryOpGen implements BinOpGen { // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); int remSize = type.partialSize(); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("temp" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) + + try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", legCount)) { + for (int i = 0; i < legCount; i++) { + mv.visitVarInsn(ISTORE, temp.idx(i)); + // NOTE: More significant legs have higher indices (reverse of stack) + } + // [lleg1,...,llegN:INT] + for (int i = 0; i < legCount; i++) { + boolean takesCarry = i != 0; // not first + IntAddOpGen.generateMpIntLegAdd(gen, temp.idx(i), takesCarry, true, false, mv); + } + // [olegN:LONG] + if (remSize == 0) { + // The last leg was full, so extract bit 32 + mv.visitLdcInsn(32); + } + else { + // The last leg was partial, so get the next more significant bit + mv.visitLdcInsn(remSize * Byte.SIZE); + } + mv.visitInsn(LUSHR); + TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); + mv.visitLdcInsn(1); + mv.visitInsn(IAND); } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean takesCarry = i != 0; // not first - IntAddOpGen.generateMpIntLegAdd(gen, firstIndex + i, takesCarry, true, mv); - } - // [olegN:LONG] - if (remSize == 0) { - // The last leg was full, so extract bit 32 - mv.visitLdcInsn(32); - } - else { - // The last leg was partial, so get the next more significant bit - mv.visitLdcInsn(remSize * Byte.SIZE); - } - mv.visitInsn(LUSHR); - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); - mv.visitLdcInsn(1); - mv.visitInsn(IAND); - mv.visitLabel(end); + } + + @Override + public boolean isSigned() { + return false; } @Override @@ -102,7 +105,7 @@ public enum IntCarryOpGen implements BinOpGen { * On the other hand, if there is room to capture the carry, we can just add the two * operands and extract the carry bit. There is no need to duplicate the left operand. */ - lType = TypeConversions.forceUniformZExt(lType, rType, rv); + lType = TypeConversions.forceUniform(gen, lType, rType, ext(), rv); switch (lType) { case IntJitType(int size) when size == Integer.BYTES -> rv.visitInsn(DUP); case IntJitType lt -> { @@ -110,7 +113,8 @@ public enum IntCarryOpGen implements BinOpGen { case LongJitType(int size) when size == Long.BYTES -> rv.visitInsn(DUP2); case LongJitType lt -> { } - case MpIntJitType lt -> TODO("MpInt"); + case MpIntJitType lt -> { + } default -> throw new AssertionError(); } return lType; @@ -119,7 +123,7 @@ public enum IntCarryOpGen implements BinOpGen { @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntCarryOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); switch (rType) { case IntJitType(int size) when size == Integer.BYTES -> { // [l,l,r] @@ -128,7 +132,7 @@ public enum IntCarryOpGen implements BinOpGen { rv.visitInsn(SWAP); // spare an LDC,XOR // [sum,l] rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", - MDESC_INTEGER__COMPARE_UNSIGNED, false); + MDESC_INTEGER__COMPARE, false); // [cmpU(sum,l)] sum < l iff sign bit is 1 rv.visitLdcInsn(31); rv.visitInsn(IUSHR); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java index e15b508d19..8d8506dae1 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java @@ -15,7 +15,6 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; @@ -36,29 +35,40 @@ import ghidra.pcode.emu.jit.op.JitIntDivOp; * {@link Integer#divideUnsigned(int, int)} or {@link Long#divideUnsigned(long, long)} depending on * the type. */ -public enum IntDivOpGen implements BinOpGen { +public enum IntDivOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + private void generateMpIntDiv(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntDivide", mv, 1, TakeOut.OUT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntDivOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntDivOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); switch (rType) { case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "divideUnsigned", MDESC_$INT_BINOP, false); case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "divideUnsigned", MDESC_$LONG_BINOP, false); - case MpIntJitType t -> TODO("MpInt"); + case MpIntJitType t when t.size() == lType.size() -> generateMpIntDiv(gen, t, rv); + // FIXME: forceUniform shouldn't have to enforce the same size.... + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return lType; + // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations. + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java index 9ca3b2fa54..a2618f65fc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java @@ -29,6 +29,11 @@ public enum IntLeftOpGen implements ShiftIntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public String methodName() { return "intLeft"; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java index cab8efca0a..82b9c65ec8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java @@ -15,8 +15,6 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; @@ -24,6 +22,7 @@ import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitIntMultOp; /** @@ -33,27 +32,63 @@ import ghidra.pcode.emu.jit.op.JitIntMultOp; * This uses the binary operator generator and simply emits {@link #IMUL} or {@link #LMUL} depending * on the type. */ -public enum IntMultOpGen implements BinOpGen { +public enum IntMultOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + /** + * Generate the mp-int multiply code. + *

+ * NOTE: I'd really like to know how many legs of the input operands are actually + * relevant. Very often, the following idiom is used: + * + *

+	 * temp: 16 = zext(r1) * zext(r2);
+	 * r0 = temp(0);
+	 * 
+ *

+ * That ensures all the operand sizes match, which is often (at least conventionally) required + * by the Sleigh compiler. However, if r1 and r2 are each only 64 bits, and I can keep track of + * that fact, then I could perform about half as many multiplies and adds. It also be nice if I + * can look ahead and see that only 64 bits of temp is actually used. + *

+ * IDEA: It would be quite a change, but perhaps generating a temporary JVM-level DFG + * would be useful for culling. The difficulty here is knowing whether or not a temp (unique) is + * used by a later cross-build. Maybe with the right API calls, I could derive that without + * additional Sleigh compiler support. If used, I should not cull any computations, so that the + * retired value is the full value. + * + * @param gen the code generator + * @param type the (uniform) type of the inputs and output operands + * @param mv the method visitor + */ + private void generateMpIntMult(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntMultiply", mv, 0, TakeOut.OUT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntMultOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntMultOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); switch (rType) { case IntJitType t -> rv.visitInsn(IMUL); case LongJitType t -> rv.visitInsn(LMUL); - case MpIntJitType t -> TODO("MpInt"); + case MpIntJitType t when t.size() == lType.size() -> generateMpIntMult(gen, t, rv); + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return lType; + // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations. + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java index a903b58a99..690462b051 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java @@ -17,7 +17,7 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; @@ -32,23 +32,45 @@ import ghidra.pcode.emu.jit.op.JitIntNegateOp; * compiler for int negate(n) {return ~n;}. It XORs the input with a register of 1s. * This uses the unary operator generator and emits the equivalent code. */ -public enum IntNegateOpGen implements UnOpGen { +public enum IntNegateOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; // TODO: Is it? Test with 3-byte operands to figure it out. + } + + private void generateMpIntNegate(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + int legCount = type.legsAlloc(); + try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", legCount)) { + for (int i = 0; i < legCount; i++) { + mv.visitVarInsn(ISTORE, temp.idx(i)); + // NOTE: More significant legs have higher indices (reverse of stack) + } + // Compute and push back in reverse order + int i = legCount; + for (SimpleJitType t : type.legTypes()) { + mv.visitVarInsn(ILOAD, temp.idx(--i)); + mv.visitLdcInsn(-1 >>> (Integer.SIZE - t.size() * Byte.SIZE)); + mv.visitInsn(IXOR); + } + } + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntNegateOp op, JitBlock block, JitType uType, MethodVisitor rv) { switch (uType) { case IntJitType t -> { - rv.visitInsn(ICONST_M1); + rv.visitLdcInsn(-1 >>> (Integer.SIZE - t.size() * Byte.SIZE)); rv.visitInsn(IXOR); } case LongJitType t -> { - rv.visitLdcInsn(-1L); + rv.visitLdcInsn(-1L >>> (Long.SIZE - t.size() * Byte.SIZE)); rv.visitInsn(LXOR); } - case MpIntJitType t -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> generateMpIntNegate(gen, t, rv); default -> throw new AssertionError(); } return uType; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java index 3bc4e50936..bb85065519 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java @@ -15,7 +15,6 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; @@ -35,29 +34,40 @@ import ghidra.pcode.emu.jit.op.JitIntRemOp; * {@link Integer#remainderUnsigned(int, int)} or {@link Long#remainderUnsigned(long, long)} * depending on the type. */ -public enum IntRemOpGen implements BinOpGen { +public enum IntRemOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + private void generateMpIntRem(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntDivide", mv, 1, TakeOut.LEFT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntRemOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntRemOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); switch (rType) { case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "remainderUnsigned", MDESC_$INT_BINOP, false); case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "remainderUnsigned", MDESC_$LONG_BINOP, false); - case MpIntJitType t -> TODO("MpInt"); + case MpIntJitType t when t.size() == lType.size() -> generateMpIntRem(gen, t, rv); + // FIXME: forceUniform shouldn't have to enforce the same size.... + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } // TODO: For MpInt case, we should use the outvar's size to cull operations. - return lType; + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java index 7640a069e2..8905fa84ef 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java @@ -29,6 +29,11 @@ public enum IntRightOpGen implements ShiftIntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public String methodName() { return "intRight"; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java index 09bfc6eaaf..408d5d1c40 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java @@ -15,7 +15,6 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; @@ -26,6 +25,7 @@ import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitIntSBorrowOp; /** @@ -37,20 +37,25 @@ import ghidra.pcode.emu.jit.op.JitIntSBorrowOp; * {@link JitCompiledPassage#sBorrowLongRaw(long, long)} depending on the type. We must then emit a * shift and mask to extract the correct bit. */ -public enum IntSBorrowOpGen implements BinOpGen { +public enum IntSBorrowOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSBorrowOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.SIGN, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSBorrowOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.SIGN, rv); switch (rType) { case IntJitType(int size) -> { rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sBorrowIntRaw", @@ -74,7 +79,7 @@ public enum IntSBorrowOpGen implements BinOpGen { return IntJitType.I1; } case MpIntJitType t -> { - return TODO("MpInt"); + return IntSCarryOpGen.generateMpIntSCarry(gen, t, "sBorrowMpInt", rv); } default -> throw new AssertionError(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java index 6228aeb689..99dd1b166f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java @@ -15,11 +15,12 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; @@ -37,20 +38,39 @@ import ghidra.pcode.emu.jit.op.JitIntSCarryOp; * {@link JitCompiledPassage#sCarryLongRaw(long, long)} depending on the type. We must then emit a * shift and mask to extract the correct bit. */ -public enum IntSCarryOpGen implements BinOpGen { +public enum IntSCarryOpGen implements IntBinOpGen { /** The generator singleton */ GEN; @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSCarryOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + public boolean isSigned() { + return true; + } + + static IntJitType generateMpIntSCarry(JitCodeGenerator gen, MpIntJitType type, + String methodName, MethodVisitor mv) { + JitAllocationModel am = gen.getAllocationModel(); + int legCount = type.legsAlloc(); + try ( + JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", legCount); + JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", legCount)) { + OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); + OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); + + OpGen.generateMpLegsIntoArray(tmpL, legCount, legCount, mv); + OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); + mv.visitLdcInsn((type.size() % Integer.BYTES) * Byte.SIZE - 1); + + mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName, + MDESC_JIT_COMPILED_PASSAGE__S_CARRY_MP_INT, true); + } + return IntJitType.I4; } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSCarryOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); switch (rType) { case IntJitType(int size) -> { rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sCarryIntRaw", @@ -74,7 +94,7 @@ public enum IntSCarryOpGen implements BinOpGen { return IntJitType.I1; } case MpIntJitType t -> { - return TODO("MpInt"); + return generateMpIntSCarry(gen, t, "sCarryMpInt", rv); } default -> throw new AssertionError(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java index 76ddbf3fdc..c720f98d4a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java @@ -15,8 +15,6 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; @@ -33,24 +31,36 @@ import ghidra.pcode.emu.jit.op.JitIntSDivOp; * This uses the binary operator generator and simply emits {@link #IDIV} or {@link #LDIV} depending * on the type. */ -public enum IntSDivOpGen implements BinOpGen { +public enum IntSDivOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + + private void generateMpIntSDiv(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntSignedDivide", mv, 1, + TakeOut.OUT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSDivOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSDivOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); switch (rType) { case IntJitType t -> rv.visitInsn(IDIV); case LongJitType t -> rv.visitInsn(LDIV); - case MpIntJitType t -> TODO("MpInt"); + case MpIntJitType t when t.size() == lType.size() -> generateMpIntSDiv(gen, t, rv); + // FIXME: forceUniform shouldn't have to enforce the same size.... + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } // TODO: For MpInt case, we should use the outvar's size to cull operations. diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java index 129597f661..ee4437291a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java @@ -17,12 +17,11 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.op.JitIntSExtOp; /** @@ -35,43 +34,18 @@ import ghidra.pcode.emu.jit.op.JitIntSExtOp; * {@link IntJitType#I4 int4} to {@link LongJitType#I8 int8} is implemented with by emitting only * {@link #I2L}. */ -public enum IntSExtOpGen implements UnOpGen { +public enum IntSExtOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntSExtOp op, JitBlock block, JitType uType, MethodVisitor rv) { - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - - if (uType == IntJitType.I4 && outType == LongJitType.I8) { - rv.visitInsn(I2L); - return outType; - } - - TypeConversions.generate(gen, uType, outType, rv); - switch (outType) { - case IntJitType t -> { - int shamt = Integer.SIZE - op.u().size() * Byte.SIZE; - if (shamt != 0) { - rv.visitLdcInsn(shamt); - rv.visitInsn(ISHL); - rv.visitLdcInsn(shamt); - rv.visitInsn(ISHR); - } - } - case LongJitType t -> { - int shamt = Long.SIZE - op.u().size() * Byte.SIZE; - if (shamt != 0) { - rv.visitLdcInsn(shamt); - rv.visitInsn(LSHL); - rv.visitLdcInsn(shamt); - rv.visitInsn(LSHR); - } - } - case MpIntJitType t -> Unfinished.TODO("MpInt"); - default -> throw new AssertionError(); - } - return outType; + return uType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java index 94397ee823..ac9a6daa8c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java @@ -17,7 +17,6 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; @@ -32,24 +31,36 @@ import ghidra.pcode.emu.jit.op.JitIntSRemOp; * This uses the binary operator generator and simply emits {@link #IREM} or {@link #LREM} depending * on the type. */ -public enum IntSRemOpGen implements BinOpGen { +public enum IntSRemOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + + private void generateMpIntSRem(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntSignedDivide", mv, 1, + TakeOut.LEFT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSRemOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSRemOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); switch (rType) { case IntJitType t -> rv.visitInsn(IREM); case LongJitType t -> rv.visitInsn(LREM); - case MpIntJitType t -> Unfinished.TODO("MpInt"); + case MpIntJitType t when t.size() == lType.size() -> generateMpIntSRem(gen, t, rv); + // FIXME: forceUniform shouldn't have to enforce the same size.... + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } // TODO: For MpInt case, we should use the outvar's size to cull operations. diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java index ffe78ec626..c8c10dc824 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java @@ -15,12 +15,7 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.op.JitIntSRightOp; /** @@ -35,14 +30,12 @@ public enum IntSRightOpGen implements ShiftIntBinOpGen { GEN; @Override - public String methodName() { - return "intSRight"; + public boolean isSigned() { + return true; } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSRightOp op, JitType lType, JitType rType, - MethodVisitor rv) { - TypeConversions.generateSExt(lType, rv); - return lType; + public String methodName() { + return "intSRight"; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java index d52c1174d1..a19730af1b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java @@ -17,13 +17,15 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.*; +import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitIntSubOp; /** @@ -36,10 +38,15 @@ import ghidra.pcode.emu.jit.op.JitIntSubOp; *

* NOTE: The multi-precision integer parts of this are a work in progress. */ -public enum IntSubOpGen implements BinOpGen { +public enum IntSubOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + private void generateMpIntLegSub(JitCodeGenerator gen, int idx, boolean takesBorrow, boolean givesBorrow, MethodVisitor mv) { if (takesBorrow) { @@ -50,19 +57,19 @@ public enum IntSubOpGen implements BinOpGen { mv.visitInsn(DUP2_X1); mv.visitInsn(POP2); // [...,borrowinN:LONG,llegN:INT] - mv.visitInsn(I2L); // yes, signed + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,borrowinN:LONG,llegN:LONG] mv.visitInsn(LADD); // Yes, add, because borrow is 0 or -1 // [...,diffpartN:LONG] } else { // [...,legN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,diffpartN:LONG] (legN + 0) } mv.visitVarInsn(ILOAD, idx); // [...,diffpartN:LONG,rlegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,diffpartN:LONG,rlegN:LONG] mv.visitInsn(LSUB); // [...,olegN:LONG] @@ -70,7 +77,7 @@ public enum IntSubOpGen implements BinOpGen { mv.visitInsn(DUP2); } // [...,(olegN:LONG),olegN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); + TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); // [...,(olegN:LONG),olegN:INT] /** NB. The store will perform the masking */ mv.visitVarInsn(ISTORE, idx); @@ -82,46 +89,34 @@ public enum IntSubOpGen implements BinOpGen { * The strategy is to allocate a temp local for each leg of the result. First, we'll pop the * right operand into the temp. Then, as we work with each leg of the left operand, we'll * execute the algorithm. Convert both right and left legs to a long and add them (along - * with a possible carry in). Store the result back into the temp locals. Shift the leg + * with a possible borrow in). Store the result back into the temp locals. Shift the leg * right 32 to get the carry out, then continue to the next leg up. The final carry out can * be dropped (overflow). The result legs are then pushed back to the stack. */ // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); // include partial - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("result" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) + try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { + OpGen.generateMpLegsIntoTemp(result, legCount, mv); + // [lleg1,...,llegN:INT] + for (int i = 0; i < legCount; i++) { + boolean isLast = i == legCount - 1; + boolean takesCarry = i != 0; // not first + generateMpIntLegSub(gen, result.idx(i), takesCarry, !isLast, mv); + } + OpGen.generateMpLegsFromTemp(result, legCount, mv); } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - boolean takesCarry = i != 0; // not first - generateMpIntLegSub(gen, firstIndex + i, takesCarry, !isLast, mv); - } - - // Push it all back, in reverse order - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); - } - mv.visitLabel(end); } @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSubOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSubOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); switch (rType) { case IntJitType t -> rv.visitInsn(ISUB); case LongJitType t -> rv.visitInsn(LSUB); @@ -129,6 +124,6 @@ public enum IntSubOpGen implements BinOpGen { case MpIntJitType t -> TODO("MpInt of differing sizes"); default -> throw new AssertionError(); } - return lType; + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java new file mode 100644 index 0000000000..5cf18e9e27 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java @@ -0,0 +1,27 @@ +/* ### + * 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.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.op.JitIntUnOp; + +/** + * An extension for integer unary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntUnOpGen extends UnOpGen { + // Intentionally empty +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java index d66a9746a6..7e4c2e1eeb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java @@ -35,10 +35,22 @@ import ghidra.pcode.emu.jit.op.JitIntZExtOp; * Note that this implementation is equivalent to {@link CopyOpGen}, except that differences in * operand sizes are expected. */ -public enum IntZExtOpGen implements UnOpGen { +public enum IntZExtOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + /** + * {@inheritDoc} + * + * @implNote No need for explicit zero-extended type conversion (vice {@link IntSExtOpGen}), + * because conversion will happen as a manner of writing the output. Thus, this is + * identical in operation to {@link CopyOpGen}. + */ @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntZExtOp op, JitBlock block, JitType uType, MethodVisitor rv) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java index c3d0fc295b..b9f6d2562c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java @@ -26,6 +26,7 @@ import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.type.*; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitLoadOp; import ghidra.program.model.lang.Endian; @@ -184,9 +185,9 @@ public enum LoadOpGen implements OpGen { // [...] gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); // [...,space] - JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType()); + JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO); // [...,space,offset:?INT/LONG] - TypeConversions.generateToLong(offsetType, LongJitType.I8, rv); + TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv); // [...,space,offset:LONG] rv.visitLdcInsn(op.out().size()); // [...,space,offset,size] @@ -204,7 +205,7 @@ public enum LoadOpGen implements OpGen { default -> throw new AssertionError(); } // [...,value] - gen.generateVarWriteCode(op.out(), outType); + gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); // [...] } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java index ca1847599d..2f7e076a0d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java @@ -15,9 +15,10 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; +import org.bouncycastle.util.Bytes; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; @@ -34,19 +35,77 @@ import ghidra.pcode.emu.jit.op.JitLzCountOp; * {@link Integer#numberOfLeadingZeros(int)} or {@link Long#numberOfLeadingZeros(long)}, depending * on the type. */ -public enum LzCountOpGen implements UnOpGen { +public enum LzCountOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + /** + * We use zero extension and then, when there is slack, we subtract off the zero bits that + * came from the extension. + */ + return false; + } + + private void generateMpIntLzCount(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + // [leg1:INT,...,legN:INT] + mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); + // [lzc1:INT,leg2:INT,...,legN:INT] + for (int i = 1; i < type.legsAlloc(); i++) { + mv.visitInsn(SWAP); + // [leg2:INT,lzc1:INT,...,legN:INT] + mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); + // [lzc2:INT,lzc1:INT,...,legN:INT] + + Label lblAdd = new Label(); + Label lblNext = new Label(); + mv.visitInsn(DUP); + mv.visitLdcInsn(Integer.SIZE); + mv.visitJumpInsn(IF_ICMPEQ, lblAdd); + // [lzc2:INT,lzc1:INT,...,legN:INT] + mv.visitInsn(SWAP); + mv.visitInsn(POP); + // [lzc2:INT,...,legN:INT] + mv.visitJumpInsn(GOTO, lblNext); + mv.visitLabel(lblAdd); + // [lzc2:INT,lzc1:INT,...,legN:INT] + mv.visitInsn(IADD); + // [lzc2+lzc1:INT,...,legN:INT] + mv.visitLabel(lblNext); + // [lzcT:INT,...,legN:INT] + } + + SimpleJitType mslType = type.legTypes().get(0); + if (mslType.size() < Integer.BYTES) { + mv.visitLdcInsn(Integer.SIZE - mslType.size() * Byte.SIZE); + mv.visitInsn(ISUB); + } + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitLzCountOp op, JitBlock block, JitType uType, MethodVisitor rv) { switch (uType) { - case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, - "numberOfLeadingZeros", MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); - case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, - "numberOfLeadingZeros", MDESC_LONG__NUMBER_OF_LEADING_ZEROS, false); - case MpIntJitType t -> TODO("MpInt"); + case IntJitType t -> { + rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); + if (t.size() < Integer.BYTES) { + rv.visitLdcInsn(Integer.SIZE - t.size() * Byte.SIZE); + rv.visitInsn(ISUB); + } + } + case LongJitType t -> { + rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "numberOfLeadingZeros", + MDESC_LONG__NUMBER_OF_LEADING_ZEROS, false); + if (t.size() < Long.BYTES) { + rv.visitLdcInsn(Long.SIZE - t.size() * Bytes.SIZE); + rv.visitInsn(ISUB); + } + } + case MpIntJitType t -> generateMpIntLzCount(gen, t, rv); default -> throw new AssertionError(); } return IntJitType.I4; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java index 3607703d2e..654abc790e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java @@ -15,14 +15,19 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; +import java.io.PrintStream; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.objectweb.asm.*; import ghidra.pcode.emu.jit.analysis.*; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.var.*; @@ -562,6 +567,148 @@ public interface OpGen extends Opcodes { }; } + /** + * Emit bytecode to move all legs from the stack into a temporary allocation + *

+ * This consumes {@code legCount} legs from the stack. Nothing else is pushed to the stack. The + * legs are placed in ascending indices as popped from the stack, i.e., in little-endian order. + * + * @param temp the allocation of temporary legs + * @param legCount the number of legs to move + * @param mv the method visitor + */ + static void generateMpLegsIntoTemp(JvmTempAlloc temp, int legCount, MethodVisitor mv) { + // [leg1,...,legN] + for (int i = 0; i < legCount; i++) { + mv.visitVarInsn(ISTORE, temp.idx(i)); + } + // [] + } + + /** + * Emit bytecode to move all legs from a temporary allocation onto the stack + *

+ * This consumes nothing. It places {@code legCount} legs onto the stack, pushed in descending + * order, i.e., such that they would be popped in little-endian order. + * + * @param temp the allocation of temporary legs + * @param legCount the number of lets to move + * @param mv the method visitor + */ + static void generateMpLegsFromTemp(JvmTempAlloc temp, int legCount, MethodVisitor mv) { + // [] + for (int i = 0; i < legCount; i++) { + mv.visitVarInsn(ILOAD, temp.idx(legCount - i - 1)); + } + // [leg1,...,legN] + } + + /** + * Emit bytecode to copy all legs from a temporary allocation into an array + *

+ * This does not consume anything from the stack. Upon return, the new array is pushed onto the + * stack. The legs are positioned in the array in the same order as in the locals. When used + * with {@link #generateMpLegsIntoTemp(JvmTempAlloc, int, MethodVisitor)}, this is little-endian + * order. + * + * @param temp the allocation of temporary legs + * @param arrSize the size of the array, possibly over-provisioned + * @param legCount the number of legs to move + * @param mv the method visitor + */ + static void generateMpLegsIntoArray(JvmTempAlloc temp, int arrSize, int legCount, + MethodVisitor mv) { + assert arrSize >= legCount; + // [] + mv.visitLdcInsn(arrSize); + // [count:INT] + mv.visitIntInsn(NEWARRAY, T_INT); + // [arr:INT[count]] + for (int i = 0; i < legCount; i++) { + mv.visitInsn(DUP); + // [arr,arr:INT[count]] + mv.visitLdcInsn(i); + // [idx:INT,arr,arr:INT[count]] + mv.visitVarInsn(ILOAD, temp.idx(i)); + // [leg:INT,idx:INT,arr,arr:INT[count]] + mv.visitInsn(IASTORE); + // [arr:INT[count]] + } + } + + /** + * Emit bytecode to push all legs from an array onto the stack + *

+ * This consumes the array at the top of the stack, and pushes its legs onto the stack in the + * reverse order as they are positioned in the array. If the legs are in little-endian order, as + * is convention, this method will push the legs to the stack with the least-significant leg on + * top. + * + * @param legCount the number of legs in the array + * @param mv the method visitor + */ + static void generateMpLegsFromArray(int legCount, MethodVisitor mv) { + // [out:INT[count]] + for (int i = 0; i < legCount - 1; i++) { + // [out] + mv.visitInsn(DUP); + // [out,out] + mv.visitLdcInsn(legCount - 1 - i); + // [idx,out,out] + mv.visitInsn(IALOAD); + // [legN:INT,out] + mv.visitInsn(SWAP); + // [out,legN:INT] + } + mv.visitLdcInsn(0); + // [idx,out,...] + mv.visitInsn(IALOAD); + // [leg1,...] + } + + static void generateSyserrInts(JitCodeGenerator gen, int count, MethodVisitor mv) { + try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", count)) { + // [leg1,...,legN] + generateMpLegsIntoTemp(temp, count, mv); + // [] + mv.visitFieldInsn(GETSTATIC, Type.getInternalName(System.class), "err", + Type.getDescriptor(PrintStream.class)); + // [System.err] + String fmt = + IntStream.range(0, count).mapToObj(i -> "%08x").collect(Collectors.joining(":")); + mv.visitLdcInsn(fmt); + // [fmt:String,System.err] + + mv.visitLdcInsn(count); + // [count,fmt,System.err] + mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class)); + // [blegs:Object[count],fmt,System.err] + for (int i = 0; i < count; i++) { + mv.visitInsn(DUP); + // [blegs,blegs,fmt,System.err] + mv.visitLdcInsn(i); + // [idx:INT,blegs,blegs,fmt,System.err] + mv.visitVarInsn(ILOAD, temp.idx(count - i - 1)); + // [val:INT,idx,blegs,blegs,fmt,System.err] + mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", + Type.getMethodDescriptor(Type.getType(Integer.class), Type.INT_TYPE), false); + // [val:Integer,idx,blegs,blegs,fmt,System.err] + mv.visitInsn(AASTORE); + // [blegs,fmt,System.err] + } + + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(String.class), "formatted", + Type.getMethodDescriptor(Type.getType(String.class), Type.getType(Object[].class)), + false); + // [msg:String,System.err] + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false); + // [] + generateMpLegsFromTemp(temp, count, mv); + // [leg1,...,legN] + } + } + /** * Emit bytecode into the class constructor. * @@ -579,9 +726,9 @@ public interface OpGen extends Opcodes { * This method must emit the code needed to load any input operands, convert them to the * appropriate type, perform the actual operation, and then if applicable, store the output * operand. The implementations should delegate to - * {@link JitCodeGenerator#generateValReadCode(JitVal, JitTypeBehavior)}, - * {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType)}, and {@link TypeConversions} - * appropriately. + * {@link JitCodeGenerator#generateValReadCode(JitVal, JitTypeBehavior, Ext)}, + * {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType, Ext)}, and + * {@link TypeConversions} appropriately. * * @param gen the code generator * @param op the p-code op (use-def node) to translate diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java index 5e5b273c6b..642b623572 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java @@ -15,7 +15,6 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; @@ -33,10 +32,30 @@ import ghidra.pcode.emu.jit.op.JitPopCountOp; * This uses the unary operator generator and emits an invocation of {@link Integer#bitCount(int)} * or {@link Long#bitCount(long)}, depending on the type. */ -public enum PopCountOpGen implements UnOpGen { +public enum PopCountOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + private void generateMpIntPopCount(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + // [leg1:INT,...,legN:INT] + mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "bitCount", MDESC_INTEGER__BIT_COUNT, false); + // [pop1:INT,leg2:INT...,legN:INT] + for (int i = 1; i < type.legsAlloc(); i++) { + mv.visitInsn(SWAP); + // [leg2:INT,pop1:INT,...,legN:INT] + mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "bitCount", MDESC_INTEGER__BIT_COUNT, + false); + // [pop2:INT,pop1:INT,...,legN:INT] + mv.visitInsn(IADD); + // [popT:INT,...,legN:INT] + } + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitPopCountOp op, JitBlock block, JitType uType, MethodVisitor rv) { @@ -45,7 +64,7 @@ public enum PopCountOpGen implements UnOpGen { MDESC_INTEGER__BIT_COUNT, false); case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "bitCount", MDESC_LONG__BIT_COUNT, false); - case MpIntJitType t -> TODO("MpInt"); + case MpIntJitType t -> generateMpIntPopCount(gen, t, rv); default -> throw new AssertionError(); } return IntJitType.I4; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java index 980e68681d..65acabca9b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java @@ -19,12 +19,14 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; -import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitIntBinOp; /** @@ -37,7 +39,17 @@ import ghidra.pcode.emu.jit.op.JitIntBinOp; * * @param the class of p-code op node in the use-def graph */ -public interface ShiftIntBinOpGen extends BinOpGen { +public interface ShiftIntBinOpGen extends IntBinOpGen { + /** + * {@inheritDoc} + *

+ * The shift amount is always treated unsigned. + */ + @Override + default Ext rExt() { + return Ext.ZERO; + } + /** * The name of the static method in {@link JitCompiledPassage} to invoke * @@ -45,6 +57,89 @@ public interface ShiftIntBinOpGen extends BinOpGen { */ String methodName(); + default MpIntJitType generateShiftMpPrimitive(JitAllocationModel am, int legCount, + SimpleJitType rType, MpIntJitType outType, String mdesc, MethodVisitor mv) { + try ( + JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", legCount); + JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", rType.javaType(), 1)) { + // [amt:INT, lleg1:INT,...,llegN:INT] + mv.visitVarInsn(rType.opcodeStore(), tmpR.idx(0)); + // [lleg1,...,llegN] + OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); + // [] + /** + * FIXME: We could avoid this array allocation by shifting in place, but then we'd still + * need to communicate the actual out size. Things are easy if the out size is smaller + * than the left-in size, but not so easy if larger. Or, maybe over-provision if + * larger.... + */ + mv.visitLdcInsn(outType.legsAlloc()); + // [outLegCount:INT] + mv.visitIntInsn(NEWARRAY, T_INT); + // [out:ARR] + mv.visitInsn(DUP); + // [out,out] + mv.visitLdcInsn(outType.size()); + // [outBytes:INT,out,out] + OpGen.generateMpLegsIntoArray(tmpL, legCount, legCount, mv); + // [inL:ARR,outBytes:INT,out,out] + mv.visitVarInsn(rType.opcodeLoad(), tmpR.idx(0)); + // [inR:SIMPLE,inL:ARR,outBytes,out,out] + mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true); + // [out] + OpGen.generateMpLegsFromArray(outType.legsAlloc(), mv); + // [oleg1,...,olegN] + } + return outType.ext(); + } + + default SimpleJitType generateShiftPrimitiveMp(JitAllocationModel am, SimpleJitType lType, + int legCount, String mdesc, MethodVisitor mv) { + try (JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", legCount)) { + // [rleg1:INT,...,rlegN:INT,val:INT] + OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); + // [val:INT] + OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); + // [inR:ARR,val:INT] + mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true); + // [out:INT] + } + return lType.ext(); + } + + default MpIntJitType generateShiftMpMp(JitAllocationModel am, int leftLegCount, + int rightLegCount, MpIntJitType outType, MethodVisitor mv) { + try ( + JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", leftLegCount); + JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", rightLegCount)) { + // [rleg1:INT,...,rlegN:INT,lleg1:INT,...,llegN:INT] + OpGen.generateMpLegsIntoTemp(tmpR, rightLegCount, mv); + // [lleg1,...,llegN] + OpGen.generateMpLegsIntoTemp(tmpL, leftLegCount, mv); + // [] + // FIXME: Same as in shiftPrimitiveMp + int outLegCount = outType.legsAlloc(); + mv.visitLdcInsn(outLegCount); + // [outLegCount:INT] + mv.visitIntInsn(NEWARRAY, T_INT); + // [out:ARR] + mv.visitInsn(DUP); + // [out,out] + mv.visitLdcInsn(outType.size()); + // [outBytes:INT,out,out] + OpGen.generateMpLegsIntoArray(tmpL, leftLegCount, leftLegCount, mv); + // [inL:ARR,outBytes,out,out] + OpGen.generateMpLegsIntoArray(tmpR, rightLegCount, rightLegCount, mv); + // [inR,inL,outBytes,out,out] + mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), + MDESC_$SHIFT_AA, true); + // [out] + OpGen.generateMpLegsFromArray(outLegCount, mv); + // [oleg1,...,olegN] + } + return outType; + } + /** * {@inheritDoc} * @@ -55,20 +150,49 @@ public interface ShiftIntBinOpGen extends BinOpGen { @Override default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - String mdesc = switch (lType) { + JitAllocationModel am = gen.getAllocationModel(); + return switch (lType) { case IntJitType lt -> switch (rType) { - case IntJitType rt -> MDESC_$SHIFT_II; - case LongJitType rt -> MDESC_$SHIFT_IJ; + case IntJitType rt -> { + rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), + MDESC_$SHIFT_II, true); + yield lType.ext(); + } + case LongJitType rt -> { + rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), + MDESC_$SHIFT_IJ, true); + yield lType.ext(); + } + case MpIntJitType rt -> generateShiftPrimitiveMp(am, lt, rt.legsAlloc(), + MDESC_$SHIFT_IA, rv); default -> throw new AssertionError(); }; + case LongJitType lt -> switch (rType) { - case IntJitType rt -> MDESC_$SHIFT_JI; - case LongJitType rt -> MDESC_$SHIFT_JJ; + case IntJitType rt -> { + rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), + MDESC_$SHIFT_JI, true); + yield lType.ext(); + } + case LongJitType rt -> { + rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), + MDESC_$SHIFT_JJ, true); + yield lType.ext(); + } + case MpIntJitType rt -> generateShiftPrimitiveMp(am, lt, rt.legsAlloc(), + MDESC_$SHIFT_JA, rv); + default -> throw new AssertionError(); + }; + case MpIntJitType lt -> switch (rType) { + case IntJitType rt -> generateShiftMpPrimitive(am, lt.legsAlloc(), rt, + MpIntJitType.forSize(op.out().size()), MDESC_$SHIFT_AI, rv); + case LongJitType rt -> generateShiftMpPrimitive(am, lt.legsAlloc(), rt, + MpIntJitType.forSize(op.out().size()), MDESC_$SHIFT_AJ, rv); + case MpIntJitType rt -> generateShiftMpMp(am, lt.legsAlloc(), rt.legsAlloc(), + MpIntJitType.forSize(op.out().size()), rv); default -> throw new AssertionError(); }; default -> throw new AssertionError(); }; - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true); - return lType.ext(); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java index 4e4efb9a53..9e5cff18bc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java @@ -26,6 +26,7 @@ import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.type.*; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitStoreOp; import ghidra.program.model.lang.Endian; @@ -184,11 +185,11 @@ public enum StoreOpGen implements OpGen { // [...] gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); // [...,space] - JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType()); + JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO); // [...,space,offset:?] - TypeConversions.generateToLong(offsetType, LongJitType.I8, rv); + TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv); // [...,space,offset:LONG] - JitType valueType = gen.generateValReadCode(op.value(), op.valueType()); + JitType valueType = gen.generateValReadCode(op.value(), op.valueType(), Ext.ZERO); // [...,space,offset,value] rv.visitLdcInsn(op.value().size()); // [...,space,offset,value,size] diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java index dbbd64fbf8..88000acaee 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java @@ -15,12 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.*; +import org.objectweb.asm.MethodVisitor; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitSubPieceOp; /** @@ -42,18 +45,23 @@ public enum SubPieceOpGen implements OpGen { GEN; /** - * WIP: Assumes the previous (next more significant) leg is on the stack and the current - * (unshifted) leg is in the given variable. Computes the resulting output leg and puts in into - * the given local variable, but leaves a copy of the current unshifted leg on the stack. + * Assumes the next-more-significant leg (i.e., the one from the previous iteration) is on + * the stack and the current (unshifted) leg is in the given variable. Computes the resulting + * output leg and puts in into the given local variable, but leaves a copy of the current + * unshifted leg on the stack. * * @param rv the method visitor * @param bitShift the number of bits to shift * @param index the index of the local variable for the current leg + * @implNote This cannot yet be factored with the shifting operators, because those + * take a variable for the shift amount. The subpiece offset is always constant. + * If/when we optimize shift operators with constant shift amounts, then we can + * consider factoring the common parts with this. */ - private void generateShiftWithPrevLeg(MethodVisitor rv, int bitShift, int index) { + private static void generateShiftWithPrevLeg(MethodVisitor rv, int bitShift, int index) { // [...,prevLegIn] rv.visitLdcInsn(Integer.SIZE - bitShift); - rv.visitInsn(ISHR); + rv.visitInsn(ISHL); // [...,prevLegIn:SLACK] rv.visitVarInsn(ILOAD, index); // [...,prevLegIn:SLACK,legIn] @@ -68,86 +76,85 @@ public enum SubPieceOpGen implements OpGen { // [...,legIn] } + private static MpIntJitType generateMpIntSubPiece(JitCodeGenerator gen, JitSubPieceOp op, + MpIntJitType type, MethodVisitor mv) { + MpIntJitType outMpType = MpIntJitType.forSize(op.out().size()); + int outLegCount = outMpType.legsAlloc(); + int legsLeft = type.legsAlloc(); + int popCount = op.offset() / Integer.BYTES; + int byteShift = op.offset() % Integer.BYTES; + for (int i = 0; i < popCount; i++) { + mv.visitInsn(POP); + legsLeft--; + } + + JitAllocationModel am = gen.getAllocationModel(); + try (JvmTempAlloc subpieces = am.allocateTemp(mv, "subpiece", outLegCount)) { + for (int i = 0; i < outLegCount; i++) { + mv.visitVarInsn(ISTORE, subpieces.idx(i)); + // NOTE: More significant legs have higher indices (reverse of stack) + legsLeft--; + } + + if (byteShift > 0) { + int curLeg = outLegCount - 1; + if (legsLeft > 0) { + // [...,prevLegIn] + generateShiftWithPrevLeg(mv, byteShift * Byte.SIZE, subpieces.idx(curLeg)); + // [...,legIn] + legsLeft--; + curLeg--; + } + else { + // [...] + mv.visitVarInsn(ILOAD, subpieces.idx(curLeg)); + // [...,legIn] + mv.visitInsn(DUP); + // [...,legIn,legIn] + mv.visitLdcInsn(byteShift * Byte.SIZE); + mv.visitInsn(IUSHR); + // [...,legIn,legOut] + mv.visitVarInsn(ISTORE, subpieces.idx(curLeg)); + // [...,legIn] + curLeg--; + } + while (curLeg >= 0) { + generateShiftWithPrevLeg(mv, byteShift * Byte.SIZE, subpieces.idx(curLeg)); + legsLeft--; + curLeg--; + } + } + while (legsLeft > 0) { + mv.visitInsn(POP); + legsLeft--; + } + // NOTE: More significant legs have higher indices + for (int i = outLegCount - 1; i >= 0; i--) { + mv.visitVarInsn(ILOAD, subpieces.idx(i)); + } + } + return outMpType; + } + @Override public void generateRunCode(JitCodeGenerator gen, JitSubPieceOp op, JitBlock block, MethodVisitor rv) { - JitType vType = gen.generateValReadCode(op.u(), op.uType()); - JitType outType; - switch (vType) { + JitType vType = gen.generateValReadCode(op.u(), op.uType(), Ext.ZERO); + JitType outType = switch (vType) { case IntJitType vIType -> { rv.visitLdcInsn(op.offset() * Byte.SIZE); rv.visitInsn(IUSHR); - outType = vIType; + yield vIType; } case LongJitType vLType -> { rv.visitLdcInsn(op.offset() * Byte.SIZE); rv.visitInsn(LUSHR); - outType = vLType; - } - case MpIntJitType vMpType -> { - // WIP - MpIntJitType outMpType = MpIntJitType.forSize(op.out().size()); - int outLegCount = outMpType.legsAlloc(); - int legsLeft = vMpType.legsAlloc(); - int popCount = op.offset() / Integer.BYTES; - int byteShift = op.offset() % Integer.BYTES; - for (int i = 0; i < popCount; i++) { - rv.visitInsn(POP); - } - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - rv.visitLabel(start); - for (int i = 0; i < outLegCount; i++) { - rv.visitLocalVariable("subpiece" + i, Type.getDescriptor(int.class), null, - start, end, firstIndex + i); - rv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) - legsLeft--; - } - - if (byteShift > 0) { - int curLeg = outLegCount - 1; - if (legsLeft > 0) { - // [...,prevLegIn] - generateShiftWithPrevLeg(rv, byteShift * Byte.SIZE, firstIndex + curLeg); - // [...,legIn] - legsLeft--; - curLeg--; - } - else { - // [...] - rv.visitVarInsn(ILOAD, firstIndex + curLeg); - // [...,legIn] - rv.visitInsn(DUP); - // [...,legIn,legIn] - rv.visitLdcInsn(byteShift * Byte.SIZE); - rv.visitInsn(IUSHR); - // [...,legIn,legOut] - rv.visitVarInsn(ISTORE, firstIndex + curLeg); - // [...,legIn] - curLeg--; - } - while (curLeg >= 0) { - generateShiftWithPrevLeg(rv, byteShift * Byte.SIZE, firstIndex + curLeg); - legsLeft--; - curLeg--; - } - } - while (legsLeft > 0) { - rv.visitInsn(POP); - legsLeft--; - } - // NOTE: More significant legs have higher indices - for (int i = outLegCount - 1; i >= 0; i--) { - rv.visitVarInsn(ILOAD, firstIndex + i); - } - rv.visitLabel(end); - outType = outMpType; + yield vLType; } + case MpIntJitType vMpType -> generateMpIntSubPiece(gen, op, vMpType, rv); default -> throw new AssertionError(); - } - gen.generateVarWriteCode(op.out(), outType); + }; + gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java index 5ebe23d1c0..a2f87f58ee 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java @@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitUnOp; /** @@ -29,6 +30,26 @@ import ghidra.pcode.emu.jit.op.JitUnOp; */ public interface UnOpGen extends OpGen { + /** + * Whether this operator is signed + *

+ * In many cases, the operator itself is not affected by the signedness of the operands; + * however, if size adjustments to the operands are needed, this can determine how those + * operands are extended. + * + * @return true for signed, false if not + */ + boolean isSigned(); + + /** + * When loading and storing variables, the kind of extension to apply + * + * @return the extension kind + */ + default Ext ext() { + return Ext.forSigned(isSigned()); + } + /** * Emit code for the unary operator * @@ -56,8 +77,8 @@ public interface UnOpGen extends OpGen { */ @Override default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { - JitType uType = gen.generateValReadCode(op.u(), op.uType()); + JitType uType = gen.generateValReadCode(op.u(), op.uType(), ext()); JitType outType = generateUnOpRunCode(gen, op, block, uType, rv); - gen.generateVarWriteCode(op.out(), outType); + gen.generateVarWriteCode(op.out(), outType, ext()); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java index 774bc04916..3ca550fc95 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java @@ -17,6 +17,8 @@ package ghidra.pcode.emu.jit.gen.tgt; import java.math.BigInteger; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.objectweb.asm.Opcodes; @@ -1058,6 +1060,35 @@ public interface JitCompiledPassage { return a; } + /** + * The implementation of {@link PcodeOp#INT_SCARRY int_sborrow} on multi-precision ints. + * + * @param a the first operand as in {@code a - b} + * @param b the second operand as in {@code a - b} + * @param shift one less than the number of bits in each most-significant leg, i.e., the number + * of bits to shift right such that the most-significant bit of the most-significant + * leg becomes the least-significant bit of the most-significant leg. + * @return the one carry bit + */ + static int sBorrowMpInt(int[] a, int[] b, int shift) { + assert a.length == b.length; + long carry = 0; + for (int i = 0; i < a.length; i++) { + carry >>= Integer.SIZE; + carry += (a[i] & MASK_I2UL) - (b[i] & MASK_I2UL); + } + int msr = (int) carry; + int msa = a[a.length - 1]; + int msb = b[b.length - 1]; + + msa ^= msr; + msr ^= msb; + msr ^= -1; + msa &= msr; + + return (msa >> shift) & 1; + } + /** * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on JVM ints. * @@ -1098,6 +1129,199 @@ public interface JitCompiledPassage { return r; } + /** + * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on multi-precision ints. + * + * @param a the first operand as in {@code a + b} + * @param b the second operand as in {@code a + b} + * @param shift one less than the number of bits in each most-significant leg, i.e., the number + * of bits to shift right such that the most-significant bit of the most-significant + * leg becomes the least-significant bit of the most-significant leg. + * @return the one carry bit + */ + static int sCarryMpInt(int[] a, int[] b, int shift) { + assert a.length == b.length; + long carry = 0; + for (int i = 0; i < a.length; i++) { + carry >>>= Integer.SIZE; + carry += (a[i] & MASK_I2UL) + (b[i] & MASK_I2UL); + } + int msr = (int) carry; + int msa = a[a.length - 1]; + int msb = b[b.length - 1]; + + msr ^= msa; + msa ^= msb; + msa ^= -1; + msr &= msa; + + return (msr >> shift) & 1; + } + + enum MpShiftPrivate { + ; + static void shl(int[] out, int[] val, int amt) { + int legs = amt >>> 5; + int bits = amt & 0x1f; + /*for (int i = 0; i < out.length && i < legs; i++) { + out[i] = 0; + }*/ + if (bits == 0) { + for (int i = 0; i < val.length - legs & i < out.length - legs; i++) { + out[i + legs] = val[i]; + } + return; + } + int prev = 0; + for (int i = 0; i < val.length - legs & i < out.length - legs; i++) { + out[i + legs] = (val[i] << bits) | (prev >>> (Integer.SIZE - bits)); + prev = val[i]; + } + } + + static void ushr(int[] out, int[] val, int amt) { + int legs = amt >>> 5; + int bits = amt & 0x1f; + /*for (int i = 0; i < out.length && i < legs; i++) { + out[i + legs] = 0; + }*/ + if (bits == 0) { + for (int i = 0; i < val.length - legs & i < out.length; i++) { + out[i] = val[i + legs]; + } + return; + } + int prev = 0; + for (int i = Math.min(val.length - legs, out.length) - 1; i >= 0; i--) { + out[i] = (val[i + legs] >>> bits) | (prev << (Integer.SIZE - bits)); + prev = val[i + legs]; + } + } + + static void sshr(int[] out, int[] val, int amt, int sign) { + int legs = amt >>> 5; + int bits = amt & 0x1f; + if (bits == 0) { + for (int i = 0; i < val.length - legs & i < out.length; i++) { + out[i] = val[i + legs]; + } + if (sign != 0) { + for (int i = val.length - legs; i < out.length; i++) { + out[i] = sign; + } + } + return; + } + int prev = 0; + // Only apply signed shift to most-significant leg of val + if (val.length - legs - 1 >= 0) { + out[val.length - legs - 1] = (val[val.length - 1] >> bits); + prev = val[val.length - 1]; + } + for (int i = Math.min(val.length - legs, out.length) - 2; i >= 0; i--) { + out[i] = (val[i + legs] >>> bits) | (prev << (Integer.SIZE - bits)); + prev = val[i + legs]; + } + if (sign != 0) { + for (int i = val.length - legs; i < out.length; i++) { + out[i] = sign; + } + } + } + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on multi-precision ints. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val << amt}, in little-endian order + * @param amt the amt as in {@code val << amt}, in little-endian order + */ + static void intLeft(int[] out, int outBytes, int[] val, int[] amt) { + if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + Arrays.fill(out, 0); + return; + } + } + MpShiftPrivate.shl(out, val, amt[0]); + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on an mp-int with a JVM long shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val << amt}, in little-endian order + * @param amt the amt as in {@code val << amt} + */ + static void intLeft(int[] out, int outBytes, int[] val, long amt) { + if (Long.compareUnsigned(amt, (outBytes & MASK_I2UL) * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.shl(out, val, (int) amt); + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on an mp-int with a JVM int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val << amt}, in little-endian order + * @param amt the amt as in {@code val << amt} + */ + static void intLeft(int[] out, int outBytes, int[] val, int amt) { + if (Integer.compareUnsigned(amt, outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.shl(out, val, amt); + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM long with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val << amt} + * @param amt the amt as in {@code val << amt}, in little-endian order + * @return the value + */ + static long intLeft(long val, int[] amt) { + if (Long.compareUnsigned(Integer.toUnsignedLong(amt[0]), Long.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val << amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM longs. * @@ -1134,6 +1358,30 @@ public interface JitCompiledPassage { return val << amt; } + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM int with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val << amt} + * @param amt the amt as in {@code val << amt}, in little-endian order + * @return the value + */ + static long intLeft(int val, int[] amt) { + if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val << amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM int with long amt. * @@ -1170,6 +1418,98 @@ public interface JitCompiledPassage { return val << amt; } + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on multi-precision ints. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val >> amt}, in little-endian order + * @param amt the amt as in {@code val >> amt}, in little-endian order + */ + static void intRight(int[] out, int outBytes, int[] val, int[] amt) { + if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + Arrays.fill(out, 0); + return; + } + } + MpShiftPrivate.ushr(out, val, amt[0]); + } + + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on an mp-int with a JVM long shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val >> amt}, in little-endian order + * @param amt the amt as in {@code val >> amt} + */ + static void intRight(int[] out, int outBytes, int[] val, long amt) { + if (Long.compareUnsigned(amt, (outBytes & MASK_I2UL) * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.ushr(out, val, (int) amt); + } + + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on an mp-int with a JVM int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val >> amt}, in little-endian order + * @param amt the amt as in {@code val >> amt} + */ + static void intRight(int[] out, int outBytes, int[] val, int amt) { + if (Integer.compareUnsigned(amt, outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.ushr(out, val, amt); + } + + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM long with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val >> amt} + * @param amt the amt as in {@code val >> amt}, in little-endian order + * @return the value + */ + static long intRight(long val, int[] amt) { + if (Long.compareUnsigned(Integer.toUnsignedLong(amt[0]), Long.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val >>> amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM longs. * @@ -1206,6 +1546,30 @@ public interface JitCompiledPassage { return val >>> amt; } + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM int with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val >> amt} + * @param amt the amt as in {@code val >> amt}, in little-endian order + * @return the value + */ + static long intRight(int val, int[] amt) { + if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val >>> amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM int with long amt. * @@ -1242,6 +1606,34 @@ public interface JitCompiledPassage { return val >>> amt; } + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_sright} on multi-precision ints. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with + * the sign bit. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val s>> amt}, in little-endian order + * @param amt the amt as in {@code val s>> amt}, in little-endian order + */ + static void intSRight(int[] out, int outBytes, int[] val, int[] amt) { + int sign = val[val.length - 1] < 0 ? -1 : 0; + if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, sign); + return; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + Arrays.fill(out, sign); + return; + } + } + MpShiftPrivate.sshr(out, val, amt[0], sign); + } + /** * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM longs. * @@ -1318,6 +1710,323 @@ public interface JitCompiledPassage { return val >> amt; } + static final long MASK_I2UL = 0x0000_0000_ffff_ffffL; + + /** + * The implementation of {@link PcodeOp#INT_MULT} on mp-ints. + *

+ * All arrays are in little-endian order + * + * @param out the array allocated to receive the output + * @param inL the array of left input legs + * @param inR the array of right input legs + */ + static void mpIntMultiply(int[] out, int[] inL, int[] inR) { + long carry = 0; + long rp = inR[0] & MASK_I2UL; + for (int li = 0; li < inL.length && li < out.length; li++) { + long lp = inL[li] & MASK_I2UL; + carry += lp * rp; + out[li] = (int) carry; + carry >>>= Integer.SIZE; + } + + for (int ri = 1; ri < inR.length && ri < out.length; ri++) { + carry = 0; + rp = inR[ri] & MASK_I2UL; + for (int li = 0; li < inL.length && ri + li < out.length; li++) { + long lp = inL[li] & MASK_I2UL; + long op = out[li + ri] & MASK_I2UL; + carry += op + lp * rp; + out[li + ri] = (int) carry; + carry >>>= Integer.SIZE; + } + } + } + + public static String mpToString(int[] legs) { + if (legs == null) { + return "null"; + } + List list = IntStream.of(legs).mapToObj(i -> "%08x".formatted(i)).toList(); + return list.reversed().stream().collect(Collectors.joining(":")); + } + + enum MpDivPrivate { + ; + + /** + * Count the number of leading 0 bits in the first non-zero leg, and identify that leg's + * index + * + * @param legs + */ + static int lz(int[] legs) { + // Least-significant leg is first + int count = 0; + for (int i = legs.length - 1; i >= 0; i--) { + int llz = Integer.numberOfLeadingZeros(legs[i]); + count += llz; + if (llz != Integer.SIZE) { + break; + } + } + return count; + } + + static int size(int[] legs) { + // Least-significant leg is first + for (int i = legs.length - 1; i >= 0; i--) { + if (legs[i] != 0) { + return i + 1; + } + } + return 0; + } + + /** + * Shift the given mp-int legs left the given number of bits + * + * @param legs the legs + * @param shift the number of bits to shift left + */ + static void shl(int[] legs, int shift) { + if (shift == 0) { + return; // The extra leading leg is already 0 + } + assert shift >= 0 && shift < Integer.SIZE; + + long carry = 0; + for (int i = 0; i < legs.length; i++) { + carry |= (legs[i] & MASK_I2UL) << shift; + legs[i] = (int) carry; + carry >>>= Integer.SIZE; + } + } + + static void shr(int[] legs, int shift) { + if (shift == 0) { + return; + } + assert shift >= 0 && shift < Integer.SIZE; + + long carry = 0; + for (int i = legs.length - 1; i >= 0; i--) { + carry |= (legs[i] & MASK_I2UL) << (Integer.SIZE - shift); + legs[i] = (int) (carry >> Integer.SIZE); + carry <<= Integer.SIZE; + } + } + + /** + * Perform unsigned division for a multi-precision dividend and single-precision divisor + * + * @param out the output for the quotient + * @param inL the dividend, and the output for the remainder + * @param sizeL the number of legs in the dividend + * @param inR the divisor + */ + static void divideMpSp(int[] out, int[] inL, int sizeL, int inR) { + long r = 0; + for (int j = sizeL - 1; j >= 0; j--) { + r <<= Integer.SIZE; + r += inL[j] & MASK_I2UL; + out[j] = (int) (r / inR); + r %= inR; + inL[j] = 0; // So that the mp-int inL is truly the remainder + } + inL[0] = (int) r; + } + + /** + * Perform unsigned division (or division of magnitudes) + * + * @param out the output for the quotient + * @param inL the dividend, and the output for the remainder + * @param inR the divisor + * @implNote this is just Algorithm D from Knuth's TAOCP Volume 2 without any sophisticated + * optimizations. We don't really need to optimize for the "big" case, we just + * need to support the bigger-than-a-machine-word case. + */ + static void divide(int[] out, int[] inL, int[] inR) { + /** + * Before we mutate anything, compute lengths for D2. We'll compute sizeR from the + * leading-zeroes computation in D1. + */ + int sizeL = size(inL); + + /** + * D1 [Normalize] + * + * My understanding of this step is to assure that the divisor (inR) has a 1 in the + * most-significant bit of its most-significant leg ("digit" in the text's terminology). + */ + int shiftBits = lz(inR); + + int truncR = shiftBits / Integer.SIZE; + int sizeR = inR.length - truncR; + + if (sizeR == 1) { + /** + * Never mind all this. Just do the simple algorithm (Exercise 16, in TAOCP Vol. 2, + * Section 4.3.1). We actually can't use the full multi-precision algorithm, because + * the adjustment of qHat in step D3 assumes the size of the divisor is >= 2 legs. + */ + divideMpSp(out, inL, sizeL, inR[0]); + return; + } + + int shift = shiftBits % Integer.SIZE; + + shl(inL, shift); + shl(inR, shift); + + /** + * D2 [Initialize j] + * + * What should be an easy step here is complicated by the fact that every + * operand has to (conventionally) have equal size is Sleigh. Thus, we need to seek out + * the most-significant leg with a non-zero value for each, and then compute j. Probably + * need to avoid an off-by-one error here, too. + * + * dividend has size m + n "sizeL" (text calls dividend u_{m+n-1}...u_0) + * + * divisor has size n "sizeR" (text calls divisor v_{n-1}...v_0}) + * + * Thus m = sizeL - sizeR + */ + for (int j = sizeL - sizeR; j >= 0; j--) { // step and test are D7 + /** + * D3 [Calculate q\^] + */ + // NOTE That inL is over-provisioned by 1, so we're good to index m+n + long qHat = (inL[sizeR + j] & MASK_I2UL) << Integer.SIZE; + qHat |= inL[sizeR + j - 1] & MASK_I2UL; + long rHat = qHat; + long vNm1 = inR[sizeR - 1] & MASK_I2UL; // v_{n-1} + qHat /= vNm1; + rHat %= vNm1; + + do { + if (qHat == 1L << Integer.SIZE || Long.compareUnsigned( + qHat * (inR[sizeR - 2] & MASK_I2UL), + (rHat << Integer.SIZE) + (inL[sizeR + j - 2] & MASK_I2UL)) > 0) { + qHat--; + rHat += vNm1; + } + else { + break; + } + } + while (Long.compareUnsigned(rHat, 1L << Integer.SIZE) < 0); + + /** + * D4 [Multiply and subtract] + * + * NOTE: borrow will become -1 if a borrow is needed, so add it to each subsequent + * leg and use signed shift. + */ + long borrow = 0; + for (int i = 0; i < sizeR - 1; i++) { + borrow = (inL[j + i] & MASK_I2UL) - qHat * (inR[i] & MASK_I2UL) + borrow; + inL[j + i] = (int) borrow; + borrow >>= Integer.SIZE; + } + borrow = (inL[j + sizeR] & MASK_I2UL) + borrow; + inL[j + sizeR] = (int) borrow; + borrow >>= Integer.SIZE; + + /** + * D5 [Test remainder] + */ + if (borrow != 0) { + assert borrow == -1; + /** + * D6 [Add back] + */ + qHat--; + + long carry = 0; + for (int i = 0; i < sizeR; i++) { + carry += (inL[j + i] & MASK_I2UL) + inR[i]; + inL[j + i] = (int) carry; + carry >>>= Integer.SIZE; + } + } + out[j] = (int) qHat; // Completion of D5 + + /** + * D7 [Loop on j] + * + * The step and test of the for loop that ends here implements D7 + */ + } + /** + * D8 [Unnormalize] + */ + shr(inL, shift); + } + + static void neg(int[] legs, int count) { + long carry = 1; + for (int i = 0; i < count; i++) { + carry += (~legs[i] & MASK_I2UL); + legs[i] = (int) carry; + carry >>>= Integer.SIZE; + } + } + + static void sdivide(int[] out, int[] inL, int[] inR) { + // NOTE: inL is over-provisioned by 1 + boolean signL = inL[inL.length - 2] < 0; + boolean signR = inR[inR.length - 1] < 0; + if (signL) { + neg(inL, inL.length - 1); + } + if (signR) { + neg(inR, inR.length); + } + divide(out, inL, inR); + if (signL != signR) { + neg(out, out.length); + } + if (signL) { + neg(inL, inL.length - 1); + } + } + } + + /** + * The implementation of {@link PcodeOp#INT_DIV} on mp-ints. + *

+ * All arrays are in little-endian order. While this directly implements + * {@link PcodeOp#INT_DIV}, it is also used for {@link PcodeOp#INT_REM}, + * {@link PcodeOp#INT_SDIV}, and {@link PcodeOp#INT_SREM}. + * + * @param out the array allocated to receive the quotient + * @param inL the array of dividend input legs, over-provisioned by 1, which will also receive + * the remainder + * @param inR the array of divisor input legs + */ + static void mpIntDivide(int[] out, int[] inL, int[] inR) { + MpDivPrivate.divide(out, inL, inR); + } + + /** + * The implementation of {@link PcodeOp#INT_SDIV} on mp-ints. + *

+ * All arrays are in little-endian order. While this directly implements + * {@link PcodeOp#INT_SDIV}, it is also used for {@link PcodeOp#INT_SREM}. + * + * @param out the array allocated to receive the quotient + * @param inL the array of dividend input legs, over-provisioned by 1, which will also receive + * the remainder + * @param inR the array of divisor input legs + */ + static void mpIntSignedDivide(int[] out, int[] inL, int[] inR) { + MpDivPrivate.sdivide(out, inL, inR); + } + /** * Get the language for the given string language ID * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java index bd17bcf1b7..61129df9a7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java @@ -17,16 +17,17 @@ package ghidra.pcode.emu.jit.gen.type; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.*; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.analysis.*; +import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.op.BinOpGen; -import ghidra.pcode.emu.jit.gen.op.IntSExtOpGen; import ghidra.pcode.emu.jit.op.JitBinOp; import ghidra.program.model.pcode.PcodeOp; @@ -61,6 +62,21 @@ import ghidra.program.model.pcode.PcodeOp; * */ public interface TypeConversions extends Opcodes { + + /** + * Kinds of extension + */ + enum Ext { + /** Zero extension */ + ZERO, + /** Sign extension */ + SIGN; + + public static Ext forSigned(boolean signed) { + return signed ? SIGN : ZERO; + } + } + /** * Emit an {@link Opcodes#IAND} to reduce the number of bits to those permitted in an int of the * given size. @@ -72,12 +88,24 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor */ - static void checkGenIntMask(JitType from, IntJitType to, MethodVisitor mv) { + static void checkGenIntExt(JitType from, IntJitType to, Ext ext, MethodVisitor mv) { if (to.size() < from.size() && to.size() < Integer.BYTES) { - mv.visitLdcInsn(-1 >>> (Integer.SIZE - to.size() * Byte.SIZE)); - mv.visitInsn(IAND); + int shamt = Integer.SIZE - to.size() * Byte.SIZE; + switch (ext) { + case ZERO -> { + mv.visitLdcInsn(-1 >>> shamt); + mv.visitInsn(IAND); + } + case SIGN -> { + mv.visitLdcInsn(shamt); + mv.visitInsn(ISHL); + mv.visitLdcInsn(shamt); + mv.visitInsn(ISHR); + } + } } } @@ -86,11 +114,12 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static IntJitType generateIntToInt(IntJitType from, IntJitType to, MethodVisitor mv) { - checkGenIntMask(from, to, mv); + static IntJitType generateIntToInt(IntJitType from, IntJitType to, Ext ext, MethodVisitor mv) { + checkGenIntExt(from, to, ext, mv); return to; } @@ -99,12 +128,14 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static IntJitType generateLongToInt(LongJitType from, IntJitType to, MethodVisitor mv) { + static IntJitType generateLongToInt(LongJitType from, IntJitType to, Ext ext, + MethodVisitor mv) { mv.visitInsn(L2I); - checkGenIntMask(from, to, mv); + checkGenIntExt(from, to, ext, mv); return to; } @@ -130,10 +161,12 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static IntJitType generateMpIntToInt(MpIntJitType from, IntJitType to, MethodVisitor mv) { + static IntJitType generateMpIntToInt(MpIntJitType from, IntJitType to, Ext ext, + MethodVisitor mv) { if (to.size() == from.size()) { // We're done. The one leg on the stack becomes the int return to; @@ -146,7 +179,7 @@ public interface TypeConversions extends Opcodes { mv.visitInsn(POP); // [...,legN] } - checkGenIntMask(from, to, mv); + checkGenIntExt(from, to, ext, mv); return to; } @@ -158,16 +191,17 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static IntJitType generateToInt(JitType from, IntJitType to, MethodVisitor mv) { + static IntJitType generateToInt(JitType from, IntJitType to, Ext ext, MethodVisitor mv) { return switch (from) { - case IntJitType iFrom -> generateIntToInt(iFrom, to, mv); // in case of mask - case LongJitType lFrom -> generateLongToInt(lFrom, to, mv); + case IntJitType iFrom -> generateIntToInt(iFrom, to, ext, mv); // in case of ext + case LongJitType lFrom -> generateLongToInt(lFrom, to, ext, mv); case FloatJitType fFrom -> generateFloatToInt(fFrom, to, mv); case DoubleJitType dFrom -> throw new AssertionError("Size mismatch"); - case MpIntJitType mpFrom -> generateMpIntToInt(mpFrom, to, mv); + case MpIntJitType mpFrom -> generateMpIntToInt(mpFrom, to, ext, mv); default -> throw new AssertionError(); }; } @@ -183,12 +217,24 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor */ - static void checkGenLongMask(JitType from, LongJitType to, MethodVisitor mv) { + static void checkGenLongExt(JitType from, LongJitType to, Ext ext, MethodVisitor mv) { if (to.size() < from.size()) { - mv.visitLdcInsn(-1L >>> (Long.SIZE - to.size() * Byte.SIZE)); - mv.visitInsn(LAND); + int shamt = Long.SIZE - to.size() * Byte.SIZE; + switch (ext) { + case ZERO -> { + mv.visitLdcInsn(-1L >>> shamt); + mv.visitInsn(LAND); + } + case SIGN -> { + mv.visitLdcInsn(shamt); + mv.visitInsn(DUP); + mv.visitInsn(LSHL); + mv.visitInsn(LSHR); + } + } } } @@ -200,27 +246,37 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static LongJitType generateIntToLong(IntJitType from, LongJitType to, MethodVisitor mv) { - mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "toUnsignedLong", - MDESC_INTEGER__TO_UNSIGNED_LONG, false); + static LongJitType generateIntToLong(IntJitType from, LongJitType to, Ext ext, + MethodVisitor mv) { + switch (ext) { + case ZERO -> mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "toUnsignedLong", + MDESC_INTEGER__TO_UNSIGNED_LONG, false); + case SIGN -> { + generateSExt(from, mv); + mv.visitInsn(I2L); + } + } // In theory, never necessary, unless long is used temporarily with size 1-4. - checkGenLongMask(from, to, mv); + checkGenLongExt(from, to, ext, mv); return to; } /** - * Emit bytecode to convert one p-code in (in a JVM long) to another + * Emit bytecode to convert one p-code int (in a JVM long) to another * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static LongJitType generateLongToLong(LongJitType from, LongJitType to, MethodVisitor mv) { - checkGenLongMask(from, to, mv); + static LongJitType generateLongToLong(LongJitType from, LongJitType to, Ext ext, + MethodVisitor mv) { + checkGenLongExt(from, to, ext, mv); return to; } @@ -246,12 +302,14 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static LongJitType generateMpIntToLong(MpIntJitType from, LongJitType to, MethodVisitor mv) { + static LongJitType generateMpIntToLong(MpIntJitType from, LongJitType to, Ext ext, + MethodVisitor mv) { if (from.legsAlloc() == 1) { - generateIntToLong(IntJitType.forSize(from.size()), to, mv); + generateIntToLong(IntJitType.forSize(from.size()), to, ext, mv); return to; } // Remove all but the 2 least-significant legs @@ -265,7 +323,7 @@ public interface TypeConversions extends Opcodes { // [...,legN-1,legN] } mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "conv2IntToLong", - MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, false); + MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, true); return to; } @@ -277,16 +335,17 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static LongJitType generateToLong(JitType from, LongJitType to, MethodVisitor mv) { + static LongJitType generateToLong(JitType from, LongJitType to, Ext ext, MethodVisitor mv) { return switch (from) { - case IntJitType iFrom -> generateIntToLong(iFrom, to, mv); - case LongJitType lFrom -> generateLongToLong(lFrom, to, mv); // in case of mask + case IntJitType iFrom -> generateIntToLong(iFrom, to, ext, mv); + case LongJitType lFrom -> generateLongToLong(lFrom, to, ext, mv); // in case of mask case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); case DoubleJitType dFrom -> generateDoubleToLong(dFrom, to, mv); - case MpIntJitType mpFrom -> generateMpIntToLong(mpFrom, to, mv); + case MpIntJitType mpFrom -> generateMpIntToLong(mpFrom, to, ext, mv); default -> throw new AssertionError(); }; } @@ -369,39 +428,63 @@ public interface TypeConversions extends Opcodes { * * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static MpIntJitType generateIntToMpInt(IntJitType from, MpIntJitType to, MethodVisitor mv) { + static MpIntJitType generateIntToMpInt(IntJitType from, MpIntJitType to, Ext ext, + MethodVisitor mv) { if (to.legsAlloc() == 1) { - checkGenIntMask(from, IntJitType.forSize(to.size()), mv); + checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv); return to; } // Insert as many more significant legs as needed - for (int i = 1; i < to.legsAlloc(); i++) { - mv.visitLdcInsn(0); - mv.visitInsn(SWAP); + // First, figure out what those additional legs should be + // [lsl:INT] + switch (ext) { + case ZERO -> mv.visitLdcInsn(0); // [0:I,lsl:I] + case SIGN -> { + mv.visitInsn(DUP); + // [lsl:INT,lsl:INT] + mv.visitLdcInsn(Integer.SIZE - 1); + // [31:INT,lsl:INT,lsl:INT] + mv.visitInsn(ISHR); + // [sign:INT,lsl:INT] + } } + // NB. Because "from" is the least-significant leg, I can just do this repeatedly. + // Do two! less: + // Start at 1, because the lsl is already present. + // End 1 before total, because last op will be SWAP instead. + for (int i = 1; i < to.legsAlloc() - 1; i++) { + mv.visitInsn(DUP_X1); + // [sign:INT,lsl:INT,sign:INT,...] + } + mv.visitInsn(SWAP); + // [lsl:INT,sign:INT,sign:INT,...] return to; } /** - * Emit bytecode to convert a p-code int that its int a JVM long to multi-precision int. + * Emit bytecode to convert a p-code int that is in a JVM long to multi-precision int. * + * @param gen the code generator * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ - static MpIntJitType generateLongToMpInt(LongJitType from, MpIntJitType to, MethodVisitor mv) { + static MpIntJitType generateLongToMpInt(JitCodeGenerator gen, LongJitType from, MpIntJitType to, + Ext ext, MethodVisitor mv) { if (to.legsAlloc() == 1) { mv.visitInsn(L2I); - checkGenIntMask(from, IntJitType.forSize(to.size()), mv); + checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv); return to; } if (from.size() <= Integer.BYTES) { mv.visitInsn(L2I); - generateIntToMpInt(IntJitType.forSize(from.size()), to, mv); + generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv); return to; } // Convert, then insert as many more significant legs as needed @@ -413,29 +496,53 @@ public interface TypeConversions extends Opcodes { mv.visitLdcInsn(Integer.SIZE); mv.visitInsn(LUSHR); mv.visitInsn(L2I); - /** This is the upper leg, which may need masking */ - checkGenIntMask(IntJitType.forSize(from.size() - Integer.BYTES), - IntJitType.forSize(to.partialSize()), mv); - // [val:LONG,msl:INT] - mv.visitInsn(DUP_X2); - // [msl:INT,val:LONG,msl:INT] - mv.visitInsn(POP); - // [msl:INT,val:LONG] - mv.visitInsn(L2I); - // [msl:INT,lsl:INT] - // Now add legs - if (to.legsAlloc() > 2) { - mv.visitLdcInsn(0); - // [msl:INT,lsl:INT,0] - for (int i = 2; i < to.legsAlloc(); i++) { - // [msl:INT,lsl:INT,0] - mv.visitInsn(DUP_X2); - // [0,msl:INT,lsl:INT,0] + /** This is the upper leg, which may need extending */ + if (to.size() < Long.BYTES) { + checkGenIntExt(IntJitType.forSize(from.size() - Integer.BYTES), + IntJitType.forSize(to.size() - Integer.BYTES), ext, mv); + } + + int tempCount = switch (ext) { + case ZERO -> 0; + case SIGN -> 1; + }; + try (JvmTempAlloc sign = gen.getAllocationModel().allocateTemp(mv, "sign", tempCount)) { + switch (ext) { + case ZERO -> { + } + case SIGN -> { + mv.visitInsn(DUP); + mv.visitLdcInsn(Integer.SIZE - 1); + mv.visitInsn(ISHR); + mv.visitVarInsn(ISTORE, sign.idx(0)); + } } - // [...,0,msl:INT,lsl:INT,0] + + // [val:LONG,msl:INT] + mv.visitInsn(DUP_X2); + // [msl:INT,val:LONG,msl:INT] mv.visitInsn(POP); - // [...,0,msl:INT,lsl:INT] + // [msl:INT,val:LONG] + mv.visitInsn(L2I); + // [msl:INT,lsl:INT] + + // Now add legs + if (to.legsAlloc() > 2) { + switch (ext) { + case ZERO -> mv.visitLdcInsn(0); + case SIGN -> mv.visitVarInsn(ILOAD, sign.idx(0)); + } + // [msl:INT,lsl:INT,sign:INT] + for (int i = 2; i < to.legsAlloc(); i++) { + // [msl:INT,lsl:INT,sign:INT] + mv.visitInsn(DUP_X2); + // [sign:INT,msl:INT,lsl:INT,sign:INT] + } + // [...,sign:INT,msl:INT,lsl:INT,sign:INT] + mv.visitInsn(POP); + // [...,sign:INT,msl:INT,lsl:INT] + } } return to; } @@ -446,22 +553,23 @@ public interface TypeConversions extends Opcodes { * @param gen the code generator * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ static MpIntJitType generateMpIntToMpInt(JitCodeGenerator gen, MpIntJitType from, - MpIntJitType to, MethodVisitor mv) { + MpIntJitType to, Ext ext, MethodVisitor mv) { if (to.size() == from.size()) { // Nothing to convert return to; } // Some special cases to avoid use of local variables: if (to.legsAlloc() == 1) { - generateMpIntToInt(from, IntJitType.forSize(to.size()), mv); + generateMpIntToInt(from, IntJitType.forSize(to.size()), ext, mv); return to; } if (from.legsAlloc() == 1) { - generateIntToMpInt(IntJitType.forSize(from.size()), to, mv); + generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv); return to; } @@ -469,42 +577,44 @@ public interface TypeConversions extends Opcodes { int legsIn = from.legsAlloc(); int legsOut = to.legsAlloc(); int localsCount = Integer.min(legsIn, legsOut); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label localsStart = new Label(); - Label localsEnd = new Label(); - mv.visitLabel(localsStart); - for (int i = 0; i < localsCount; i++) { - mv.visitLocalVariable("temp" + i, Type.getDescriptor(int.class), null, localsStart, - localsEnd, firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - } - // Add or remove legs - int toAdd = legsOut - legsIn; - for (int i = 0; i < toAdd; i++) { - mv.visitLdcInsn(0); - } - int toRemove = legsIn - legsOut; - for (int i = 0; i < toRemove; i++) { - mv.visitInsn(POP); - } + try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", localsCount)) { + for (int i = 0; i < localsCount; i++) { + mv.visitVarInsn(ISTORE, temp.idx(i)); + } - // Start pushing them back, but the most significant may need masking - int idx = firstIndex + localsCount; - idx--; - mv.visitVarInsn(ILOAD, idx); - if (to.size() < from.size()) { - checkGenIntMask( - from, // already checked size, so anything greater - IntJitType.forSize(to.partialSize()), mv); - } - // push the rest back - for (int i = 0; i < localsCount; i++) { - idx--; - mv.visitVarInsn(ILOAD, idx); - } + // Add or remove legs + int toAdd = legsOut - legsIn; + if (toAdd >= 1) { + switch (ext) { + case ZERO -> mv.visitLdcInsn(0); + case SIGN -> { + mv.visitVarInsn(ILOAD, temp.idx(localsCount - 1)); + mv.visitLdcInsn(Integer.SIZE - 1); + mv.visitInsn(ISHR); + } + } + } + for (int i = 1; i < toAdd; i++) { + mv.visitInsn(DUP); + } + int toRemove = -toAdd; + for (int i = 0; i < toRemove; i++) { + mv.visitInsn(POP); + } - mv.visitLabel(localsEnd); + // Start pushing them back, but the most significant may need extending + mv.visitVarInsn(ILOAD, temp.idx(localsCount - 1)); + if (to.size() < from.size()) { + checkGenIntExt( + from, // already checked size, so anything greater + IntJitType.forSize(to.partialSize()), ext, mv); + } + // push the rest back + for (int i = 1; i < localsCount; i++) { + mv.visitVarInsn(ILOAD, temp.idx(localsCount - i - 1)); + } + } return to; } @@ -518,17 +628,18 @@ public interface TypeConversions extends Opcodes { * @param gen the code generator * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the destination type */ static MpIntJitType generateToMpInt(JitCodeGenerator gen, JitType from, MpIntJitType to, - MethodVisitor mv) { + Ext ext, MethodVisitor mv) { return switch (from) { - case IntJitType iFrom -> generateIntToMpInt(iFrom, to, mv); - case LongJitType lFrom -> generateLongToMpInt(lFrom, to, mv); + case IntJitType iFrom -> generateIntToMpInt(iFrom, to, ext, mv); + case LongJitType lFrom -> generateLongToMpInt(gen, lFrom, to, ext, mv); case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); case DoubleJitType dFrom -> throw new AssertionError("Size mismatch"); - case MpIntJitType mpFrom -> generateMpIntToMpInt(gen, mpFrom, to, mv); + case MpIntJitType mpFrom -> generateMpIntToMpInt(gen, mpFrom, to, ext, mv); default -> throw new AssertionError(); }; } @@ -543,16 +654,18 @@ public interface TypeConversions extends Opcodes { * @param gen the code generator * @param from the source type * @param to the destination type + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the resulting (destination) type */ - static JitType generate(JitCodeGenerator gen, JitType from, JitType to, MethodVisitor mv) { + static JitType generate(JitCodeGenerator gen, JitType from, JitType to, Ext ext, + MethodVisitor mv) { return switch (to) { - case IntJitType iTo -> generateToInt(from, iTo, mv); - case LongJitType lTo -> generateToLong(from, lTo, mv); + case IntJitType iTo -> generateToInt(from, iTo, ext, mv); + case LongJitType lTo -> generateToLong(from, lTo, ext, mv); case FloatJitType fTo -> generateToFloat(from, fTo, mv); case DoubleJitType dTo -> generateToDouble(from, dTo, mv); - case MpIntJitType mpTo -> generateToMpInt(gen, from, mpTo, mv); + case MpIntJitType mpTo -> generateToMpInt(gen, from, mpTo, ext, mv); default -> throw new AssertionError(); }; } @@ -695,9 +808,6 @@ public interface TypeConversions extends Opcodes { /** * Emit code to extend a signed value of the given type to fill its host JVM type. * - *

- * This is implemented in the same manner as {@link IntSExtOpGen int_sext}. - * * @param type the p-code type * @param mv the method visitor * @return the p-code type that exactly fits the host JVM type, i.e., the resulting p-code type. @@ -727,21 +837,6 @@ public interface TypeConversions extends Opcodes { return type.ext(); } - /** - * Convert a signed {@link IntJitType#I4 int4} to {@link LongJitType#I8 int8}. - * - *

- * Note that if conversion from a smaller int type is needed, the generator must first call - * {@link #generateSExt(JitType, MethodVisitor)}. - * - * @param mv the method visitor - * @return the resulting type ({@link LongJitType#I8 int8}) - */ - static LongJitType generateSExtIntToLong(MethodVisitor mv) { - mv.visitInsn(I2L); - return LongJitType.I8; - } - /** * Select the larger of two types and emit code to convert an unsigned value of the first type * to the host JVM type of the selected type. @@ -769,59 +864,38 @@ public interface TypeConversions extends Opcodes { * generateBinOpRunCode} if we're using it. The two resulting types should now be equal, and we * can examine them and emit the correct bytecodes. * + * @param gen the code generator * @param myType the type of an operand, probably in a binary operator * @param otherType the type of the other operand of a binary operator + * @param ext whether the extension is signed or not * @param mv the method visitor * @return the new type of the operand */ - static JitType forceUniformZExt(JitType myType, JitType otherType, MethodVisitor mv) { - return switch (myType.ext()) { - case IntJitType mt -> switch (otherType.ext()) { + static JitType forceUniform(JitCodeGenerator gen, JitType myType, JitType otherType, + Ext ext, MethodVisitor mv) { + // TODO: Why was .ext() being used here (inconsistently, too) + return switch (myType) { + case IntJitType mt -> switch (otherType) { case IntJitType ot -> mt; - case LongJitType ot -> generateIntToLong(mt, ot, mv); - case MpIntJitType ot -> generateIntToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); + case LongJitType ot -> generateIntToLong(mt, ot, ext, mv); + // FIXME: Would be nice to allow non-uniform mp-int sizes + case MpIntJitType ot -> generateIntToMpInt(mt, ot, ext, mv); default -> throw new AssertionError(); }; case LongJitType mt -> switch (otherType) { case IntJitType ot -> mt; // Other operand needs up-conversion case LongJitType ot -> mt; - case MpIntJitType ot -> generateLongToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); + // FIXME: Would be nice to allow non-uniform mp-int sizes + case MpIntJitType ot -> generateLongToMpInt(gen, mt, ot, ext, mv); default -> throw new AssertionError(); }; - case MpIntJitType mt -> mt; // Other may need up-conversion - default -> throw new AssertionError(); - }; - } - - /** - * Do the same as {@link #forceUniformZExt(JitType, JitType, MethodVisitor)}, but with signed - * values. - * - * @param myType the type of an operand, probably in a binary operator - * @param otherType the type of the other operand of a binary operator - * @param mv the method visitor - * @return the new type of the operand - */ - static JitType forceUniformSExt(JitType myType, JitType otherType, MethodVisitor mv) { - JitType myExtType = generateSExt(myType, mv); - return switch (myExtType) { - case IntJitType mt -> switch (otherType.ext()) { // Don't extend other, yet - case IntJitType ot -> mt; - case LongJitType ot -> generateSExtIntToLong(mv); - case MpIntJitType ot -> generateIntToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); - default -> throw new AssertionError(); - }; - case LongJitType mt -> switch (otherType.ext()) { + // FIXME: Would be nice to allow non-uniform mp-int sizes + case MpIntJitType mt -> switch (otherType) { case IntJitType ot -> mt; // Other operand needs up-conversion - case LongJitType ot -> mt; - case MpIntJitType ot -> generateLongToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); + case LongJitType ot -> mt; // Other operand needs up-conversion + case MpIntJitType ot -> generateMpIntToMpInt(gen, mt, ot, ext, mv); default -> throw new AssertionError(); }; - case MpIntJitType mt -> mt; // Other may need up-conversion default -> throw new AssertionError(); }; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java index 0a5c1939b9..4eb302017c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java @@ -22,6 +22,7 @@ import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitConstVal; /** @@ -41,7 +42,7 @@ public enum ConstValGen implements ValGen { @Override public JitType generateValReadCode(JitCodeGenerator gen, JitConstVal v, JitTypeBehavior typeReq, - MethodVisitor rv) { + Ext ext, MethodVisitor rv) { JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); switch (type) { case IntJitType t -> rv.visitLdcInsn(v.value().intValue()); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java index 9b50afd829..3e9724e682 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java @@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitDirectMemoryVar; /** @@ -33,7 +34,7 @@ public enum DirectMemoryVarGen implements MemoryVarGen { @Override public void generateVarWriteCode(JitCodeGenerator gen, JitDirectMemoryVar v, JitType type, - MethodVisitor rv) { + Ext ext, MethodVisitor rv) { throw new AssertionError(); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java index 59ef446a35..c9f841a575 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java @@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitFailVal; /** @@ -34,8 +35,8 @@ public enum FailValGen implements ValGen { } @Override - public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, - JitTypeBehavior typeReq, MethodVisitor rv) { + public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, JitTypeBehavior typeReq, + Ext ext, MethodVisitor rv) { throw new AssertionError(); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java index 6842fbdbf5..01d4dca5e8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java @@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitInputVar; /** @@ -32,7 +33,7 @@ public enum InputVarGen implements LocalVarGen { GEN; @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, + public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, Ext ext, MethodVisitor rv) { throw new AssertionError(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java index e7fe36c0cb..394cb08ea4 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java @@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitLocalOutVar; /** @@ -31,8 +32,8 @@ public enum LocalOutVarGen implements LocalVarGen { @Override public void generateVarWriteCode(JitCodeGenerator gen, JitLocalOutVar v, JitType type, - MethodVisitor rv) { + Ext ext, MethodVisitor rv) { VarHandler handler = gen.getAllocationModel().getHandler(v); - handler.generateStoreCode(gen, type, rv); + handler.generateStoreCode(gen, type, ext, rv); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java index b324240632..a2f840ae79 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java @@ -22,6 +22,7 @@ import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitVarnodeVar; /** @@ -41,11 +42,11 @@ public interface LocalVarGen extends VarGen { } @Override - default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, + default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) { VarHandler handler = gen.getAllocationModel().getHandler(v); JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); - handler.generateLoadCode(gen, type, rv); + handler.generateLoadCode(gen, type, ext, rv); return type; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java index 80fc3a10f9..24eb73ce3d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java @@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitMemoryOutVar; /** @@ -29,7 +30,7 @@ public enum MemoryOutVarGen implements MemoryVarGen { GEN; @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, + public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, Ext ext, MethodVisitor rv) { VarGen.generateValWriteCodeDirect(gen, v, type, rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java index d28aa4d94a..8f441c5401 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java @@ -21,6 +21,7 @@ import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; import ghidra.pcode.emu.jit.var.JitVarnodeVar; @@ -41,7 +42,7 @@ public interface MemoryVarGen extends VarGen { } @Override - default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, + default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) { return VarGen.generateValReadCodeDirect(gen, v, typeReq, rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java index c5063b2748..26c608f7f6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java @@ -25,6 +25,7 @@ import org.objectweb.asm.Opcodes; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitPhiOp; import ghidra.pcode.emu.jit.var.JitMissingVar; @@ -61,7 +62,7 @@ public enum MissingVarGen implements VarGen { @Override public JitType generateValReadCode(JitCodeGenerator gen, JitMissingVar v, - JitTypeBehavior typeReq, MethodVisitor rv) { + JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) { // [...] rv.visitTypeInsn(NEW, NAME_ASSERTION_ERROR); // [...,error:NEW] @@ -79,7 +80,7 @@ public enum MissingVarGen implements VarGen { } @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, + public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, Ext ext, MethodVisitor rv) { throw new AssertionError(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java index e83ac01118..4c1ed9f6db 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java @@ -24,6 +24,7 @@ import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.var.*; import ghidra.program.model.pcode.Varnode; @@ -143,9 +144,10 @@ public interface ValGen { * @param gen the code generator * @param v the value to read * @param typeReq the required type of the value + * @param ext the kind of extension to apply when adjusting from JVM size to varnode size * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method * @return the actual p-code type (which determines the JVM type) of the value on the stack */ - JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, + JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, MethodVisitor rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java index 678ec85c43..a5be31c50f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java @@ -30,6 +30,7 @@ import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.op.CBranchOpGen; import ghidra.pcode.emu.jit.gen.op.CallOtherOpGen; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; import ghidra.pcode.emu.jit.var.*; import ghidra.program.model.address.Address; @@ -307,7 +308,8 @@ public interface VarGen extends ValGen { * @param v the variable to write * @param type the p-code type (which also determines the expected JVM type) of the value on the * stack + * @param ext the kind of extension to apply when adjusting from varnode size to JVM size * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - void generateVarWriteCode(JitCodeGenerator gen, V v, JitType type, MethodVisitor rv); + void generateVarWriteCode(JitCodeGenerator gen, V v, JitType type, Ext ext, MethodVisitor rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java index b4a6efe62e..a58af8eca9 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java @@ -16,6 +16,7 @@ package ghidra.pcode.emu.jit.op; import java.util.List; +import java.util.Objects; import ghidra.pcode.emu.jit.analysis.JitDataFlowState.MiniDFState; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; @@ -39,6 +40,10 @@ public record JitCallOtherDefOp(PcodeOp op, JitOutVar out, JitTypeBehavior type, PcodeUseropDefinition userop, List args, List inputTypes, MiniDFState dfState) implements JitCallOtherOpIf, JitDefOp { + public JitCallOtherDefOp { + Objects.requireNonNull(type); + } + @Override public boolean canBeRemoved() { return JitCallOtherOpIf.super.canBeRemoved() && JitDefOp.super.canBeRemoved(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index a8004c20a8..455547713b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -20,6 +20,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.*; +import java.math.BigInteger; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -81,7 +82,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra opdef.posLib = pos; } }, - OUTPUT(OpOutput.class, Varnode.class) { + OUTPUT(OpOutput.class, Varnode.class, int[].class) { @Override int getPos(AnnotatedPcodeUseropDefinition opdef) { return opdef.posOut; @@ -115,11 +116,11 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } private final Class annotCls; - private final Class paramCls; + private final List> allowedClsList; - private ParamAnnotProc(Class annotCls, Class paramCls) { + private ParamAnnotProc(Class annotCls, Class... paramCls) { this.annotCls = annotCls; - this.paramCls = paramCls; + this.allowedClsList = List.of(paramCls); } abstract int getPos(AnnotatedPcodeUseropDefinition opdef); @@ -130,7 +131,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra return p.getAnnotation(annotCls) != null; } - Type getArgumentType(Type opType) { + static Type parameterize(Class paramCls, Type opType) { TypeVariable[] typeParams = paramCls.getTypeParameters(); if (typeParams.length == 0) { return paramCls; @@ -141,6 +142,25 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra throw new AssertionError(); } + String nameAllowedArgumentTypes(Type opType) { + return allowedClsList.stream() + .map(cls -> parameterize(cls, opType).toString()) + .collect(Collectors.joining(",")); + } + + record MatchedClassWithArgs(Class paramCls, Map, Type> typeArgs) { + static MatchedClassWithArgs find(Type paramType, List> allowed) { + for (Class cls : allowed) { + Map, Type> typeArgs = + TypeUtils.getTypeArguments(paramType, cls); + if (typeArgs != null) { + return new MatchedClassWithArgs(cls, typeArgs); + } + } + return null; + } + } + void processParameterPerAnnot(AnnotatedPcodeUseropDefinition opdef, Type declClsOpType, int i, Parameter p) { if (getPos(opdef) != -1) { @@ -148,20 +168,21 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra "Can only have one parameter with @" + annotCls.getSimpleName()); } Type pType = p.getParameterizedType(); - Map, Type> typeArgs = TypeUtils.getTypeArguments(pType, paramCls); - if (typeArgs == null) { + MatchedClassWithArgs match = MatchedClassWithArgs.find(pType, allowedClsList); + if (match == null) { throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + - annotCls.getSimpleName() + " must acccept " + getArgumentType(declClsOpType)); + annotCls.getSimpleName() + " must acccept " + + nameAllowedArgumentTypes(declClsOpType)); } - if (typeArgs.isEmpty()) { + if (match.typeArgs.isEmpty()) { // Nothing } - else if (typeArgs.size() == 1) { - Type declMthOpType = typeArgs.get(paramCls.getTypeParameters()[0]); + else if (match.typeArgs.size() == 1) { + Type declMthOpType = match.typeArgs.get(match.paramCls.getTypeParameters()[0]); if (!Objects.equals(declClsOpType, declMthOpType)) { throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + annotCls.getSimpleName() + " must acccept " + - getArgumentType(declClsOpType)); + nameAllowedArgumentTypes(declClsOpType)); } } else { @@ -324,6 +345,14 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra return canInline; } + @Override + public Class getOutputType() { + if (posOut == -1) { + return method.getReturnType(); + } + return method.getParameterTypes()[posOut]; + } + @Override public Method getJavaMethod() { return method; @@ -421,6 +450,23 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } } + record IntArrayUseropInputParam(int position) implements UseropInputParam { + @Override + public Object convert(Varnode vn, PcodeExecutor executor) { + PcodeExecutorStatePiece state = executor.getState(); + PcodeArithmetic arithmetic = executor.getArithmetic(); + BigInteger value = + arithmetic.toBigInteger(state.getVar(vn, executor.getReason()), Purpose.OTHER); + int[] result = new int[(vn.getSize() + 3) / 4]; + // This is terribly slow + for (int i = 0; i < result.length; i++) { + result[i] = value.intValue(); + value = value.shiftRight(Integer.SIZE); + } + return result; + } + } + record FloatUseropInputParam(int position) implements UseropInputParam { @Override public Object convert(Varnode vn, PcodeExecutor executor) { @@ -482,6 +528,9 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra else if (pType == long.class) { paramsIn.add(new LongUseropInputParam(i)); } + else if (pType == int[].class) { + paramsIn.add(new IntArrayUseropInputParam(i)); + } else if (pType == float.class) { paramsIn.add(new FloatUseropInputParam(i)); } @@ -735,7 +784,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * An annotation to receive the output varnode into a parameter * *

- * The annotated parameter must have type {@link Varnode}. + * The annotated parameter must have type {@link Varnode} (or {@code int[]} for direct + * invocation when the output is a multi-precision integer}). */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index cf299100af..817fa9f6ea 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -21,6 +21,7 @@ import java.util.*; import org.apache.commons.lang3.reflect.TypeUtils; import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput; import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop; import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; import ghidra.program.model.pcode.PcodeOp; @@ -219,6 +220,17 @@ public interface PcodeUseropLibrary { */ boolean canInlinePcode(); + /** + * If this userop is defined as a java callback, get the type of the output + * + *

+ * If the method has a {@code @}{@link OpOutput} annotation, this is the type of the output + * parameter. Otherwise, this is the method's return type. + * + * @return the output type + */ + Class getOutputType(); + /** * If this userop is defined as a java callback, get the method * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java index 5245b6fb47..051236009e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java @@ -221,6 +221,11 @@ public class SleighPcodeUseropDefinition implements PcodeUseropDefinition return true; } + @Override + public Class getOutputType() { + return void.class; + } + @Override public Method getJavaMethod() { return null; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java index db70edd4f8..fe44999160 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.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,38 +27,12 @@ public class OpBehaviorPopcount extends UnaryOpBehavior { @Override public long evaluateUnary(int sizeout, int sizein, long val) { - val = (val & 0x5555555555555555L) + ((val >>> 1) & 0x5555555555555555L); - val = (val & 0x3333333333333333L) + ((val >>> 2) & 0x3333333333333333L); - val = (val & 0x0f0f0f0f0f0f0f0fL) + ((val >>> 4) & 0x0f0f0f0f0f0f0f0fL); - val = (val & 0x00ff00ff00ff00ffL) + ((val >>> 8) & 0x00ff00ff00ff00ffL); - val = (val & 0x0000ffff0000ffffL) + ((val >>> 16) & 0x0000ffff0000ffffL); - int res = (int) (val & 0xff); - res += (int) ((val >> 32) & 0xff); - return res; + return Long.bitCount(val); } @Override public BigInteger evaluateUnary(int sizeout, int sizein, BigInteger unsignedIn1) { - int bitcount = 0; - while (sizein >= 8) { - bitcount += evaluateUnary(1, 8, unsignedIn1.longValue()); - sizein -= 8; - if (sizein == 0) { - break; - } - unsignedIn1 = unsignedIn1.shiftRight(64); - } - if (sizein > 0) { - long mask = sizein * 8 - 1; - bitcount += evaluateUnary(1, 8, unsignedIn1.longValue() & mask); - } - if (sizeout == 1) { - bitcount &= 0xff; - } - else if (sizeout == 2) { - bitcount &= 0xffff; - } - return BigInteger.valueOf(bitcount); + return BigInteger.valueOf(unsignedIn1.bitCount()); } } diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/JitMpIntPerformanceExperiment.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/JitMpIntPerformanceExperiment.java new file mode 100644 index 0000000000..f298d0ff63 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/JitMpIntPerformanceExperiment.java @@ -0,0 +1,323 @@ +/* ### + * 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.pcode.emu.jit; + +import java.io.File; +import java.lang.invoke.MethodHandles; +import java.math.BigInteger; + +import org.junit.*; + +import generic.test.AbstractGenericTest; +import ghidra.GhidraTestApplicationLayout; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.framework.Application; +import ghidra.framework.ApplicationConfiguration; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.DecodePcodeExecutionException; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.util.DefaultLanguageService; + +@Ignore("For developer workstation") +public class JitMpIntPerformanceExperiment { + public static final int N = 100_000_000; + public static final BigInteger MASK_16_BYTES = + BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE); + private static SleighLanguage toy; + + @BeforeClass + public static void setUp() throws Exception { + Application.initializeApplication( + new GhidraTestApplicationLayout(new File(AbstractGenericTest.getTestDirectoryPath())), + new ApplicationConfiguration()); + + toy = (SleighLanguage) DefaultLanguageService.getLanguageService() + .getLanguage(new LanguageID("Toy:BE:64:default")); + Assemblers.getAssembler(toy); + } + + @Test + public void testSpeedBigInteger() { + BigInteger previous = BigInteger.ZERO; + BigInteger current = BigInteger.ONE; + + for (int i = 0; i < N; i++) { + BigInteger next = previous.add(current); + next = next.and(MASK_16_BYTES); + previous = current; + current = next; + } + + System.out.println("fib(%d) = %s".formatted(N, current.toString(16))); + } + + @Test + public void testSpeedIntArray() { + int[] previous = new int[] { 0, 0, 0, 0 }; + int[] current = new int[] { 1, 0, 0, 0 }; + int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + doArrAdd(next, previous, current); + int[] temp = previous; + previous = current; + current = next; + next = temp; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + @Test + public void testSpeedIntArrayFinalLoop() { + final int[] previous = new int[] { 0, 0, 0, 0 }; + final int[] current = new int[] { 1, 0, 0, 0 }; + final int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + doArrAdd(next, previous, current); + for (int j = 0; j < 4; j++) { + previous[j] = current[j]; + current[j] = next[j]; + } + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + @Test + public void testSpeedIntArrayFinalArrayCopy() { + final int[] previous = new int[] { 0, 0, 0, 0 }; + final int[] current = new int[] { 1, 0, 0, 0 }; + final int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + doArrAdd(next, previous, current); + System.arraycopy(current, 0, previous, 0, 4); + System.arraycopy(next, 0, current, 0, 4); + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + private static void doArrAdd(final int[] result, final int[] a, final int[] b) { + long t = 0; + for (int i = 0; i < 4; i++) { + t += Integer.toUnsignedLong(a[i]); + t += Integer.toUnsignedLong(b[i]); + result[i] = (int) t; + t >>>= 32; + } + } + + @Test + public void testSpeedIntArrayInlined() { + int[] previous = new int[] { 0, 0, 0, 0 }; + int[] current = new int[] { 1, 0, 0, 0 }; + int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + long t = 0; + for (int j = 0; j < 4; j++) { + t += Integer.toUnsignedLong(previous[j]); + t += Integer.toUnsignedLong(current[j]); + next[j] = (int) t; + t >>>= 32; + } + + int[] temp = previous; + previous = current; + current = next; + next = temp; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + @Test + public void testSpeedScalarized() { + int prev0 = 0, prev1 = 0, prev2 = 0, prev3 = 0; + int curr0 = 1, curr1 = 0, curr2 = 0, curr3 = 0; + int next0, next1, next2, next3; + + for (int i = 0; i < N; i++) { + long t = 0; + t += Integer.toUnsignedLong(prev0); + t += Integer.toUnsignedLong(curr0); + next0 = (int) t; + t >>>= 32; + t += Integer.toUnsignedLong(prev1); + t += Integer.toUnsignedLong(curr1); + next1 = (int) t; + t >>>= 32; + t += Integer.toUnsignedLong(prev2); + t += Integer.toUnsignedLong(curr2); + next2 = (int) t; + t >>>= 32; + t += Integer.toUnsignedLong(prev3); + t += Integer.toUnsignedLong(curr3); + next3 = (int) t; + + prev0 = curr0; + prev1 = curr1; + prev2 = curr2; + prev3 = curr3; + + curr0 = next0; + curr1 = next1; + curr2 = next2; + curr3 = next3; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + curr3, curr2, curr1, curr0)); + } + + static final long LONG_MASK = 0xffffffffL; + + @Test + public void testSpeedScalarizedAnd() { + int prev0 = 0, prev1 = 0, prev2 = 0, prev3 = 0; + int curr0 = 1, curr1 = 0, curr2 = 0, curr3 = 0; + int next0, next1, next2, next3; + + for (int i = 0; i < N; i++) { + long t = 0; + t += prev0 & LONG_MASK; + t += curr0 & LONG_MASK; + next0 = (int) t; + t >>>= 32; + t += prev1 & LONG_MASK; + t += curr1 & LONG_MASK; + next1 = (int) t; + t >>>= 32; + t += prev2 & LONG_MASK; + t += curr2 & LONG_MASK; + next2 = (int) t; + t >>>= 32; + t += prev3 & LONG_MASK; + t += curr3 & LONG_MASK; + next3 = (int) t; + + prev0 = curr0; + prev1 = curr1; + prev2 = curr2; + prev3 = curr3; + + curr0 = next0; + curr1 = next1; + curr2 = next2; + curr3 = next3; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + curr3, curr2, curr1, curr0)); + } + + @Test + public void testSpeedPlainEmu() { + String sleigh = """ + counter:4 = 0; + prev:16 = 0; + curr:16 = 1; + + next:16 = prev + curr; + prev = curr; + curr = next; + counter = counter + 1; + if (counter < 0x%08x) goto ; + r0 = curr(0); + r1 = curr(8); + goto 0xdeadbeef; + """.formatted(N); + + PcodeEmulator emu = new PcodeEmulator(toy); + PcodeThread thread = emu.newThread(); + + Address address = toy.getDefaultSpace().getAddress(0x00400000); + thread.inject(address, sleigh); + thread.overrideCounter(address); + thread.reInitialize(); + + try { + thread.run(); + } + catch (DecodePcodeExecutionException e) { + if (e.getProgramCounter().getOffset() != 0xdeadbeef) { + throw e; + } + } + System.out.println("fib(%d) = %016x%016x".formatted(N, + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r1"), Reason.INSPECT), + Purpose.INSPECT), + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r0"), Reason.INSPECT), + Purpose.INSPECT))); + } + + @Test + public void testSpeedJitEmu() { + String sleigh = """ + counter:4 = 0; + prev:16 = 0; + curr:16 = 1; + + next:16 = prev + curr; + prev = curr; + curr = next; + counter = counter + 1; + if (counter < 0x%08x) goto ; + r0 = curr(0); + r1 = curr(8); + goto 0xdeadbeef; + """.formatted(N); + + JitPcodeEmulator emu = + new JitPcodeEmulator(toy, new JitConfiguration(), MethodHandles.lookup()); + JitPcodeThread thread = emu.newThread(); + + Address address = toy.getDefaultSpace().getAddress(0x00400000); + thread.inject(address, sleigh); + thread.overrideCounter(address); + thread.reInitialize(); + + try { + thread.run(); + } + catch (DecodePcodeExecutionException e) { + if (e.getProgramCounter().getOffset() != 0xdeadbeef) { + throw e; + } + } + System.out.println("fib(%d) = %016x%016x".formatted(N, + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r1"), Reason.INSPECT), + Purpose.INSPECT), + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r0"), Reason.INSPECT), + Purpose.INSPECT))); + } +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java index 61f6bce1d2..b14cc8f55e 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GAttributes.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,6 +15,7 @@ */ package generic.theme; +import java.awt.Color; import java.awt.Font; import javax.swing.text.SimpleAttributeSet; @@ -32,7 +33,7 @@ public class GAttributes extends SimpleAttributeSet { this(f, null); } - public GAttributes(Font f, GColor c) { + public GAttributes(Font f, Color c) { addAttribute(StyleConstants.FontFamily, f.getFamily()); addAttribute(StyleConstants.FontSize, f.getSize()); addAttribute(StyleConstants.Bold, f.isBold()); diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java index f6406b89d3..5a8116fc1e 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.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,7 +160,8 @@ public class GThemeDefaults { public static final GColor YELLOW = getColor("yellow"); /** - * Returns a new {@link GColor} for the given palette name. + * Returns a new {@link GColor} for the given palette name. The name should not include + * the prefix {@code color.palette}. *

* For a list of supported palette IDs, see {@code gui.palette.theme.properties}. *

diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java index 3b4efc558b..0a78fe40c6 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java @@ -33,14 +33,14 @@ import docking.action.DockingActionIf; import docking.actions.*; import docking.tool.util.DockingToolConstants; import docking.widgets.*; -import docking.widgets.label.GIconLabel; +import docking.widgets.MultiLineLabel.VerticalAlignment; import docking.widgets.table.*; import generic.theme.Gui; import ghidra.framework.options.*; import ghidra.framework.plugintool.PluginTool; -import ghidra.util.*; -import ghidra.util.layout.PairLayout; -import ghidra.util.layout.VerticalLayout; +import ghidra.framework.plugintool.ServiceProviderStub; +import ghidra.util.Msg; +import ghidra.util.Swing; import gui.event.MouseBinding; import help.Help; import help.HelpService; @@ -51,7 +51,8 @@ import resources.Icons; */ public class KeyBindingsPanel extends JPanel { - private static final int STATUS_LABEL_HEIGHT = 60; + private static final String GETTING_STARTED_MESSAGE = + "Select an action to change a keybinding"; private final static int ACTION_NAME = 0; private final static int KEY_BINDING = 1; @@ -61,7 +62,6 @@ public class KeyBindingsPanel extends JPanel { private JTextPane statusLabel; private GTable actionTable; - private JPanel infoPanel; private MultiLineLabel collisionLabel; private KeyBindingsTableModel tableModel; private ActionBindingListener actionBindingListener = new ActionBindingListener(); @@ -76,6 +76,9 @@ public class KeyBindingsPanel extends JPanel { private boolean firingTableDataChanged; private PropertyChangeListener propertyChangeListener; + private JPanel gettingStartedPanel; + private JPanel activeActionPanel; + public KeyBindingsPanel(PluginTool tool) { this.tool = tool; @@ -110,7 +113,7 @@ public class KeyBindingsPanel extends JPanel { // clear the action to avoid the appearance of editing while restoring actionTable.clearSelection(); - restoreDefaultKeybindings(); + restoreDefaultKeyBindings(); }); } @@ -135,50 +138,69 @@ public class KeyBindingsPanel extends JPanel { } private void createPanelComponents() { + setLayout(new BorderLayout(10, 10)); + // A stub panel to take up about the same amount of space as the active panel. This stub + // panel will get swapped for the active panel when a selection is made in the table. Using + // the stub panel is easier than trying to visually disable the editing widgets. + gettingStartedPanel = new JPanel(); + activeActionPanel = createActiveActionPanel(); + tableModel = new KeyBindingsTableModel(new ArrayList<>(keyBindings.getUniqueActions())); actionTable = new GTable(tableModel); - JScrollPane sp = new JScrollPane(actionTable); + JScrollPane actionsScroller = new JScrollPane(actionTable); actionTable.setPreferredScrollableViewportSize(new Dimension(400, 100)); actionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); actionTable.setHTMLRenderingEnabled(true); + actionTable.getSelectionModel().addListSelectionListener(new TableSelectionListener()); adjustTableColumns(); // middle panel - filter field and import/export buttons JPanel importExportPanel = createImportExportPanel(); tableFilterPanel = new GTableFilterPanel<>(actionTable, tableModel); - JPanel middlePanel = new JPanel(new BorderLayout()); - middlePanel.add(tableFilterPanel, BorderLayout.NORTH); - middlePanel.add(importExportPanel, BorderLayout.SOUTH); + JPanel filterAndExportsPanel = new JPanel(new BorderLayout()); + filterAndExportsPanel.add(tableFilterPanel, BorderLayout.NORTH); + filterAndExportsPanel.add(importExportPanel, BorderLayout.SOUTH); - // contains the upper panel (table) and the middle panel) + // contains the upper panel (table and the middle panel) JPanel centerPanel = new JPanel(new BorderLayout()); - centerPanel.add(sp, BorderLayout.CENTER); - centerPanel.add(middlePanel, BorderLayout.SOUTH); + centerPanel.add(actionsScroller, BorderLayout.CENTER); + centerPanel.add(filterAndExportsPanel, BorderLayout.SOUTH); + + add(centerPanel, BorderLayout.CENTER); + add(gettingStartedPanel, BorderLayout.SOUTH); + + // make both panels the same size so that as we swap them, the UI doesn't jump + Dimension preferredSize = activeActionPanel.getPreferredSize(); + gettingStartedPanel.setPreferredSize(preferredSize); + } + + private JPanel createActiveActionPanel() { // lower panel - key entry panel and status panel JPanel keyPanel = createKeyEntryPanel(); - JComponent statusPanel = createStatusPanel(keyPanel); + JPanel collisionAreaPanel = createCollisionArea(); - add(centerPanel, BorderLayout.CENTER); - add(statusPanel, BorderLayout.SOUTH); - - actionTable.getSelectionModel().addListSelectionListener(new TableSelectionListener()); + JPanel parentPanel = new JPanel(new BorderLayout()); + parentPanel.add(keyPanel, BorderLayout.NORTH); + parentPanel.add(collisionAreaPanel, BorderLayout.SOUTH); + return parentPanel; } - private JPanel createStatusPanel(JPanel keyPanel) { + private JPanel createStatusPanel() { statusLabel = new JTextPane(); statusLabel.setEnabled(false); DockingUtils.setTransparent(statusLabel); statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 5)); statusLabel.setContentType("text/html"); // render any HTML we find in descriptions + statusLabel.setText(GETTING_STARTED_MESSAGE); - // make sure the label gets enough space - statusLabel.setPreferredSize(new Dimension(0, STATUS_LABEL_HEIGHT)); + // make the label wide enough to show a line of text, but set a limit to force wrapping + statusLabel.setPreferredSize(new Dimension(300, 30)); statusLabel.setFont(Gui.getFont(FONT_ID)); helpButton = new EmptyBorderButton(Icons.HELP_ICON); @@ -189,70 +211,47 @@ public class KeyBindingsPanel extends JPanel { hs.showHelp(action, false, KeyBindingsPanel.this); }); - JPanel helpButtonPanel = new JPanel(); - helpButtonPanel.setLayout(new BoxLayout(helpButtonPanel, BoxLayout.PAGE_AXIS)); - helpButtonPanel.add(helpButton); - helpButtonPanel.add(Box.createVerticalGlue()); + JPanel statusPanel = new JPanel(); + statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.LINE_AXIS)); + statusPanel.add(helpButton); + statusPanel.add(statusLabel); - JPanel lowerStatusPanel = new JPanel(); - lowerStatusPanel.setLayout(new BoxLayout(lowerStatusPanel, BoxLayout.X_AXIS)); - lowerStatusPanel.add(helpButtonPanel); - lowerStatusPanel.add(statusLabel); + statusPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0)); - JPanel panel = new JPanel(new VerticalLayout(5)); - panel.add(keyPanel); - panel.add(lowerStatusPanel); - return panel; + return statusPanel; } private JPanel createKeyEntryPanel() { actionBindingPanel = new ActionBindingPanel(actionBindingListener); - // this is the lower panel that holds the key entry text field - JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT)); - p.add(actionBindingPanel); + // add some space at the bottom of the input area to separate it from the info area + actionBindingPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 20, 0)); JPanel keyPanel = new JPanel(new BorderLayout()); - - JPanel defaultPanel = new JPanel(new BorderLayout()); - - // the content of the left-hand side label - MultiLineLabel mlabel = - new MultiLineLabel("To add or change a key binding, select an action\n" + - "and type any key combination."); - JPanel labelPanel = new JPanel(); - labelPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0)); - BoxLayout bl = new BoxLayout(labelPanel, BoxLayout.X_AXIS); - labelPanel.setLayout(bl); - labelPanel.add(Box.createHorizontalStrut(5)); - labelPanel.add(new GIconLabel(Icons.INFO_ICON)); - labelPanel.add(Box.createHorizontalStrut(5)); - labelPanel.add(mlabel); - - // the default panel is the panel that holds left-hand side label - defaultPanel.add(labelPanel, BorderLayout.NORTH); - defaultPanel.setBorder(BorderFactory.createLoweredBevelBorder()); - - // the info panel is the holds the right-hand label and is inside of - // a scroll pane - infoPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - collisionLabel = new MultiLineLabel(" "); - collisionLabel.setName("CollisionLabel"); - - infoPanel.add(collisionLabel); - JScrollPane sp = new JScrollPane(infoPanel); - sp.setPreferredSize(defaultPanel.getPreferredSize()); - - // inner panel holds the two label panels - JPanel innerPanel = new JPanel(new PairLayout(2, 6)); - innerPanel.add(defaultPanel); - innerPanel.add(sp); - - keyPanel.add(innerPanel, BorderLayout.CENTER); - keyPanel.add(p, BorderLayout.SOUTH); + keyPanel.add(actionBindingPanel, BorderLayout.NORTH); return keyPanel; } + private JPanel createCollisionArea() { + + collisionLabel = new MultiLineLabel(" "); + collisionLabel.setVerticalAlignment(VerticalAlignment.TOP); + collisionLabel.setName("CollisionLabel"); + JScrollPane collisionScroller = new JScrollPane(collisionLabel); + int height = 100; // enough to show the typical number of collisions without scrolling + collisionScroller.setPreferredSize(new Dimension(400, height)); + + // note: we add a strut so that when the scroll pane is hidden, the size does not change + JPanel parentPanel = new JPanel(new BorderLayout()); + parentPanel.add(collisionScroller, BorderLayout.CENTER); + parentPanel.add(Box.createVerticalStrut(height), BorderLayout.WEST); + + JPanel alignmentPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + alignmentPanel.add(parentPanel); + + return alignmentPanel; + } + private JPanel createImportExportPanel() { JButton importButton = new JButton("Import..."); importButton.setToolTipText("Load key binding settings from a file"); @@ -290,11 +289,16 @@ public class KeyBindingsPanel extends JPanel { }); }); - JPanel containerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - containerPanel.add(importButton); - containerPanel.add(exportButton); + JPanel statusPanel = createStatusPanel(); - return containerPanel; + JPanel buttonPanel = new JPanel(); + buttonPanel.add(importButton); + buttonPanel.add(exportButton); + + JPanel parentPanel = new JPanel(new BorderLayout()); + parentPanel.add(statusPanel, BorderLayout.WEST); + parentPanel.add(buttonPanel, BorderLayout.EAST); + return parentPanel; } private boolean showApplyPrompt() { @@ -354,7 +358,7 @@ public class KeyBindingsPanel extends JPanel { column.setPreferredWidth(150); } - private void restoreDefaultKeybindings() { + private void restoreDefaultKeyBindings() { keyBindings.restoreOptions(); // let the table know that changes may have been made @@ -398,13 +402,20 @@ public class KeyBindingsPanel extends JPanel { } private void updateCollisionPanel(String text) { - infoPanel.removeAll(); - infoPanel.repaint(); - collisionLabel = new MultiLineLabel(text); - collisionLabel.setName("CollisionLabel"); - infoPanel.add(collisionLabel); - infoPanel.invalidate(); + + // Hide the scroll pane when there is nothing to show + Container parent = collisionLabel.getParent().getParent(); + if (text.isBlank()) { + parent.setVisible(false); + } + else { + parent.setVisible(true); + } + + collisionLabel.setLabel(text); + collisionLabel.invalidate(); validate(); + repaint(); } private void loadKeyBindingsFromImportedOptions(Options keyBindingOptions) { @@ -448,7 +459,7 @@ public class KeyBindingsPanel extends JPanel { DockingActionIf action = getSelectedAction(); if (action == null) { - statusLabel.setText("No action is selected."); + statusLabel.setText(GETTING_STARTED_MESSAGE); return; } @@ -463,7 +474,7 @@ public class KeyBindingsPanel extends JPanel { String selectedActionName = action.getFullName(); if (setActionKeyStroke(selectedActionName, ks)) { showActionsMappedToKeyStroke(ks); - tableModel.fireTableDataChanged(); + fireRowChanged(); changesMade(true); } } @@ -474,17 +485,23 @@ public class KeyBindingsPanel extends JPanel { DockingActionIf action = getSelectedAction(); if (action == null) { - statusLabel.setText("No action is selected."); + statusLabel.setText(GETTING_STARTED_MESSAGE); return; } String selectedActionName = action.getFullName(); if (setMouseBinding(selectedActionName, mb)) { - tableModel.fireTableDataChanged(); + fireRowChanged(); changesMade(true); } } + private void fireRowChanged() { + int viewRow = actionTable.getSelectedRow(); + int modelRow = tableFilterPanel.getModelRow(viewRow); + tableModel.fireTableRowsUpdated(modelRow, modelRow); + } + private boolean setMouseBinding(String actionName, MouseBinding mouseBinding) { if (keyBindings.isMouseBindingInUse(actionName, mouseBinding)) { @@ -525,9 +542,25 @@ public class KeyBindingsPanel extends JPanel { return keyBindings.getKeyStrokesByFullActionName(); } + private void swapView(JComponent newView) { + + // the lower panel we want to swap is at index 1 (index 0 is the table area) + Component component = getComponent(1); + if (component == newView) { + return; // nothing to do + } + + remove(component); + add(newView, BorderLayout.SOUTH); + Container parent = getParent(); + parent.validate(); + parent.repaint(); + } + //================================================================================================== // Inner Classes //================================================================================================== + /** * Selection listener class for the table model. */ @@ -539,15 +572,22 @@ public class KeyBindingsPanel extends JPanel { } helpButton.setEnabled(false); - String fullActionName = getSelectedActionName(); - if (fullActionName == null) { - statusLabel.setText(""); + + DockingActionIf action = getSelectedAction(); + if (action == null) { + swapView(gettingStartedPanel); + + statusLabel.setText(GETTING_STARTED_MESSAGE); actionBindingPanel.setEnabled(false); + helpButton.setToolTipText("Select action in table for help"); return; } - actionBindingPanel.setEnabled(true); + String fullActionName = getSelectedActionName(); + swapView(activeActionPanel); + + actionBindingPanel.setEnabled(true); helpButton.setEnabled(true); clearInfoPanel(); @@ -559,59 +599,54 @@ public class KeyBindingsPanel extends JPanel { MouseBinding mb = keyBindings.getMouseBinding(fullActionName); actionBindingPanel.setKeyBindingData(ks, mb); - // make sure the label gets enough space - statusLabel.setPreferredSize( - new Dimension(statusLabel.getPreferredSize().width, STATUS_LABEL_HEIGHT)); - - DockingActionIf action = getSelectedAction(); String description = action.getDescription(); - if (description == null || description.trim().isEmpty()) { + if (StringUtils.isBlank(description)) { description = action.getName(); } - statusLabel.setText("" + HTMLUtilities.escapeHTML(description)); + // Not sure why we escape the html here. Probably just to be safe. + statusLabel.setText("" + description); + helpButton.setToolTipText("Help for " + action.getName()); } } - private class KeyBindingsTableModel extends AbstractSortedTableModel { - private final String[] columnNames = { "Action Name", "KeyBinding", "Plugin Name" }; + private class KeyBindingsTableModel + extends GDynamicColumnTableModel { private List actions; - KeyBindingsTableModel(List actions) { - super(0); + public KeyBindingsTableModel(List actions) { + super(new ServiceProviderStub()); this.actions = actions; } @Override - public String getName() { - return "Keybindings"; + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); + descriptor.addVisibleColumn("Action Name", String.class, a -> a.getName(), 1, true); + descriptor.addVisibleColumn("Key Binding", String.class, a -> { + String text = ""; + String fullName = a.getFullName(); + KeyStroke ks = keyBindings.getKeyStroke(fullName); + if (ks != null) { + text += KeyBindingUtils.parseKeyStroke(ks); + } + + MouseBinding mb = keyBindings.getMouseBinding(fullName); + if (mb != null) { + text += " (" + mb.getDisplayText() + ")"; + } + + return text.trim(); + }); + descriptor.addVisibleColumn("Owner", String.class, a -> a.getOwnerDescription()); + descriptor.addHiddenColumn("Description", String.class, a -> a.getDescription()); + return descriptor; } @Override - public Object getColumnValueForRow(DockingActionIf action, int columnIndex) { - - String fullName = action.getFullName(); - switch (columnIndex) { - case ACTION_NAME: - return action.getName(); - case KEY_BINDING: - String text = ""; - KeyStroke ks = keyBindings.getKeyStroke(fullName); - if (ks != null) { - text += KeyBindingUtils.parseKeyStroke(ks); - } - - MouseBinding mb = keyBindings.getMouseBinding(fullName); - if (mb != null) { - text += " (" + mb.getDisplayText() + ")"; - } - - return text.trim(); - case PLUGIN_NAME: - return action.getOwnerDescription(); - } - return "Unknown Column!"; + public String getName() { + return "Key Bindings"; } @Override @@ -620,28 +655,8 @@ public class KeyBindingsPanel extends JPanel { } @Override - public boolean isSortable(int columnIndex) { - return true; - } - - @Override - public String getColumnName(int column) { - return columnNames[column]; - } - - @Override - public int getColumnCount() { - return columnNames.length; - } - - @Override - public int getRowCount() { - return actions.size(); - } - - @Override - public Class getColumnClass(int columnIndex) { - return String.class; + public Object getDataSource() { + return null; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java index 942d401777..b371774838 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java @@ -164,7 +164,7 @@ public class Varnode { if (spaceID != varnode.spaceID) { return false; } - if (isConstant() || isUnique() || isHash()) { + if (isConstant() || isHash()) { // this is not really a valid use case return offset == varnode.getOffset(); } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java index ad88075fbd..cb739a372f 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java @@ -51,7 +51,6 @@ import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.floatformat.FloatFormat; -import ghidra.pcode.opbehavior.OpBehaviorBoolAnd; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; @@ -78,7 +77,7 @@ public class JitCodeGeneratorTest extends AbstractJitTest { try (FileOutputStream out = new FileOutputStream(tmp)) { out.write(classbytes); } - new ProcessBuilder("javap", "-c", tmp.getPath()).inheritIO().start().waitFor(); + new ProcessBuilder("javap", "-c", "-l", tmp.getPath()).inheritIO().start().waitFor(); } record Translation(PcodeProgram program, MethodNode init, MethodNode run, JitPcodeThread thread, @@ -232,6 +231,22 @@ public class JitCodeGeneratorTest extends AbstractJitTest { return 2 * a + b; } + @PcodeUserop(functional = true) + public void func_mpUserop(@OpOutput int[] out, int[] a, int[] b) { + gotFuncUseropCall = true; + + if (out == null) { + return; + } + + out[0] = b[0]; + out[1] = a[0]; + for (int i = 0; i < 8; i++) { + out[0] |= out[0] << 4; + out[1] |= out[1] << 4; + } + } + @PcodeUserop(canInline = true) public void sleigh_userop(@OpExecutor PcodeExecutor executor, @OpLibrary PcodeUseropLibrary library, @@ -243,6 +258,12 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """, library, List.of(out, a, b)); executor.execute(opProg, library); } + + @PcodeUserop(functional = true) + public int tap_int(int a) { + System.err.println("tap: %x".formatted(a)); + return a; + } } public static class TestJitPcodeEmulator extends JitPcodeEmulator { @@ -267,7 +288,42 @@ public class JitCodeGeneratorTest extends AbstractJitTest { } } - record Case(String name, String init, List evals) {} + record Eval(String expr, BigInteger value) {} + + static Eval ev(String name, BigInteger value) { + return new Eval(name, value); + } + + static Eval ev(String name, String value) { + BigInteger bi = NumericUtilities.decodeBigInteger(value); + return ev(name, bi); + } + + static Eval ev(String name, double value) { + BigInteger bi = BigInteger.valueOf(Double.doubleToRawLongBits(value)); + return new Eval(name, bi); + } + + static Eval ev(String name, float value) { + BigInteger bi = BigInteger.valueOf(Integer.toUnsignedLong(Float.floatToRawIntBits(value))); + return new Eval(name, bi); + } + + /** + * @deprecated Because this one is accident prone when it comes to signedness. Use + * {@link #ev(String, String)} instead. + */ + @Deprecated // Just produce a warning + static Eval ev(String name, long value) { + throw new AssertionError("Use the String or BigInteger one instead"); + } + + record Case(String name, String init, List evals) {} + + static final int nNaNf = Float.floatToRawIntBits(Float.NaN) | Integer.MIN_VALUE; + static final long nNaNd = Double.doubleToRawLongBits(Double.NaN) | Long.MIN_VALUE; + static final BigInteger nNaN_F = BigInteger.valueOf(nNaNf); + static final BigInteger nNaN_D = BigInteger.valueOf(nNaNd); protected void runEquivalenceTest(Translation tr, List cases) { PcodeEmulator plainEmu = new TestPlainPcodeEmulator(tr.program.getLanguage()); @@ -282,15 +338,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { plainThread.getExecutor().execute(tr.program, plainThread.getUseropLibrary()); assertEquals("Mismatch of PC.", plainThread.getCounter().getOffset(), tr.runClean()); - for (String e : c.evals) { + for (Eval e : c.evals) { PcodeExpression expr = - SleighProgramCompiler.compileExpression(tr.program.getLanguage(), e); - BigInteger expected = plainThread.getArithmetic() + SleighProgramCompiler.compileExpression(tr.program.getLanguage(), e.expr); + BigInteger plnResult = plainThread.getArithmetic() .toBigInteger(expr.evaluate(plainThread.getExecutor()), Purpose.INSPECT); - BigInteger actual = tr.thread.getArithmetic() + BigInteger jitResult = tr.thread.getArithmetic() .toBigInteger(expr.evaluate(tr.thread.getExecutor()), Purpose.INSPECT); - assertEquals("For case '%s': Mismatch of '%s'.".formatted(c.name, e), - expected, actual); + + BigInteger expResult = + new RegisterValue(tr.program.getLanguage().getRegister(e.expr), e.value) + .getUnsignedValue(); + + assertEquals( + "WRONG ASSERTION For case '%s': Mismatch of '%s'.".formatted(c.name, e.expr), + expResult.toString(16), plnResult.toString(16)); + assertEquals("For case '%s': Mismatch of '%s'.".formatted(c.name, e.expr), + expResult.toString(16), jitResult.toString(16)); } } } @@ -417,18 +481,6 @@ public class JitCodeGeneratorTest extends AbstractJitTest { assertEquals(1.25f, Double.longBitsToDouble(tr.getLongVnVal(temp)), 0); } - @Test - @Ignore("MpInt is TODO") - public void test3LeggedInt() { - TODO(); - } - - @Test - @Ignore("MpInt is TODO") - public void test2LeggedWithResidue() { - TODO(); - } - @Test public void testReadMemMappedRegBE() throws Exception { Translation tr = translateSleigh(ID_TOYBE64, """ @@ -691,6 +743,33 @@ public class JitCodeGeneratorTest extends AbstractJitTest { assertEquals(1, tr.getLongVnVal(temp2)); } + void runTestMpIntOffcutLoad(LanguageID langID) throws Exception { + runEquivalenceTest(translateSleigh(langID, """ + local temp:16; + temp[0,64] = r1; + temp[64,64] = r2; + temp2:14 = temp[8,112]; + r0 = zext(temp2); + """), + List.of( + new Case("only", """ + r1 = 0x1122334455667788; + r2 = 0x99aabbccddeeff00; + """, + List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testMpIntOffcutLoadBE() throws Exception { + runTestMpIntOffcutLoad(ID_TOYBE64); + } + + @Test + public void testMpIntOffcutLoadLE() throws Exception { + runTestMpIntOffcutLoad(ID_TOYLE64); + } + @Test public void testCallOtherSleighDef() throws Exception { Translation tr = translateSleigh(ID_TOYBE64, """ @@ -747,6 +826,33 @@ public class JitCodeGeneratorTest extends AbstractJitTest { assertEquals(0, tr.getLongRegVal("r0")); } + @Test + public void testCallOtherFuncJavaDefMpInt() throws Exception { + Translation tr = translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(6:8); + temp2:9 = zext(2:8); + temp0:9 = func_mpUserop(temp1, temp2); + r0 = temp0(0); + """); + assertFalse(tr.library.gotFuncUseropCall); + tr.runFallthrough(); + assertTrue(tr.library.gotFuncUseropCall); + assertEquals(0x6666666622222222L, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherFuncJavaDefNoOutMpInt() throws Exception { + Translation tr = translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(6:8); + temp2:9 = zext(2:8); + func_mpUserop(temp1, temp2); + """); + assertFalse(tr.library.gotFuncUseropCall); + tr.runFallthrough(); + assertTrue(tr.library.gotFuncUseropCall); + assertEquals(0, tr.getLongRegVal("r0")); + } + /** * Test that the emulator doesn't throw until the userop is actually encountered at run time. * @@ -808,7 +914,8 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( - new Case("only", "", List.of("r0")))); + new Case("only", "", List.of( + ev("r0", "0xbeef"))))); } @Test @@ -819,7 +926,8 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r0 = 0xdead; """), List.of( - new Case("only", "", List.of("r0")))); + new Case("only", "", List.of( + ev("r0", "0xbeef"))))); } @Test @@ -831,8 +939,10 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( - new Case("take", "r1=1;", List.of("r0")), - new Case("fall", "r1=0;", List.of("r0")))); + new Case("take", "r1=1;", List.of( + ev("r0", "0xbeef"))), + new Case("fall", "r1=0;", List.of( + ev("r0", "0xdead"))))); } @Test @@ -843,8 +953,27 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r0 = 0xdead; """), List.of( - new Case("take", "r1=1;", List.of("r0")), - new Case("fall", "r1=0;", List.of("r0")))); + new Case("take", "r1=1;", List.of( + ev("r0", "0xbeef"))), + new Case("fall", "r1=0;", List.of( + ev("r0", "0xdead"))))); + } + + @Test + public void testCBranchOpGenExternalMpIntPredicate() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + r0 = 0xbeef; + temp:9 = zext(r1); + if (temp) goto 0xdeadbeef; + r0 = 0xdead; + """), + List.of( + new Case("sm_take", "r1 = 1;", List.of( + ev("r0", "0xbeef"))), + new Case("sm_fall", "r1 = 0;", List.of( + ev("r0", "0xdead"))), + new Case("lg_take", "r1 = 0x8000000000000000;", List.of( + ev("r0", "0xbeef"))))); } @Test @@ -855,24 +984,37 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( new Case("f", """ - r1 =0; - r7l =0; - """, List.of("r0", "r6")), + r1 = 0; + r7l = 0; + """, + List.of( + ev("r0", "1"), + ev("r6", "1"))), new Case("t", """ - r1 =1; - r7l =1; - """, List.of("r0", "r6")) - /*, new Case("T", """ - r1 =0x400000000; - r7l =4; - """, List.of("r0", "r6"))*/)); + r1 = 1; + r7l = 1; + """, + List.of( + ev("r0", "0"), + ev("r6", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolNegateMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp:9 = zext(r1); + temp = !temp; + r0 = temp(1); + """), + List.of( + new Case("f", """ + r1 = 0; + """, + List.of( + ev("r0", "0"))))); } - /** - * TODO: The last case (commented out) here actually fails because {@link OpBehaviorBoolAnd}, - * from the standard emulator, does bitwise AND instead of boolean AND. I do not know which - * should be corrected. - */ @Test public void testBoolAndOpGen() throws Exception { runEquivalenceTest(translateSleigh(ID_TOYBE64, """ @@ -887,33 +1029,83 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =0; r5l =0; r7l =0; r8 =0; r10l=0; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), new Case("ft", """ r1 =0; r2 =1; r4 =0; r5l =1; r7l =0; r8 =1; r10l=0; r11l=1; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), new Case("tf", """ r1 =1; r2 =0; r4 =1; r5l =0; r7l =1; r8 =0; r10l=1; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), new Case("tt", """ r1 =1; r2 =1; r4 =1; r5l =1; r7l =1; r8 =1; r10l=1; r11l=1; """, - List.of("r0", "r3", "r6", "r9")) - /*, new Case("tT", """ - r1 =100; r2 =0x400000000; - r4 =100; r5l =4; - r7l =100; r8 =0x400000000; - r10l=100; r11l=4; + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolAndMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 && temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("ff", """ + r1 = 0; r2 = 0; """, - List.of("r0", "r3", "r6", "r9"))*/)); + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("ft", """ + r1 =0; r2 = 1; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("tf", """ + r1 = 1; r2 = 0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("tt", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))))); + // NOTE: Not testing cases with other bits set } @Test @@ -930,33 +1122,83 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =0; r5l =0; r7l =0; r8 =0; r10l=0; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), new Case("ft", """ r1 =0; r2 =1; r4 =0; r5l =1; r7l =0; r8 =1; r10l=0; r11l=1; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), new Case("tf", """ r1 =1; r2 =0; r4 =1; r5l =0; r7l =1; r8 =0; r10l=1; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), new Case("tt", """ r1 =1; r2 =1; r4 =1; r5l =1; r7l =1; r8 =1; r10l=1; r11l=1; """, - List.of("r0", "r3", "r6", "r9")) - /*, new Case("tT", """ - r1 =100; r2 =0x400000000; - r4 =100; r5l =4; - r7l =100; r8 =0x400000000; - r10l=100; r11l=4; + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolOrMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 || temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; """, - List.of("r0", "r3", "r6", "r9"))*/)); + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tf", """ + r1 =1; r2 =0; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tt", """ + r1 =1; r2 =1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))))); + // NOTE: Not testing cases with other bits set } @Test @@ -973,33 +1215,83 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =0; r5l =0; r7l =0; r8 =0; r10l=0; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), new Case("ft", """ r1 =0; r2 =1; r4 =0; r5l =1; r7l =0; r8 =1; r10l=0; r11l=1; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), new Case("tf", """ r1 =1; r2 =0; r4 =1; r5l =0; r7l =1; r8 =0; r10l=1; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), new Case("tt", """ r1 =1; r2 =1; r4 =1; r5l =1; r7l =1; r8 =1; r10l=1; r11l=1; """, - List.of("r0", "r3", "r6", "r9")) - /*, new Case("tT", """ - r1 =100; r2 =0x400000000; - r4 =100; r5l =4; - r7l =100; r8 =0x400000000; - r10l=100; r11l=4; + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolXorMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 ^^ temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; """, - List.of("r0", "r3", "r6", "r9"))*/)); + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tf", """ + r1 =1; r2 =0; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tt", """ + r1 =1; r2 =1; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))))); + // NOTE: Not testing cases with other bits set } @Test @@ -1016,11 +1308,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))), new Case("n", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))))); } /** @@ -1047,11 +1345,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", Math.sqrt(0.5)), + ev("r6", (float) Math.sqrt(0.5)))), new Case("n", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", nNaN_D), + ev("r6l", nNaN_F))))); } @Test @@ -1068,11 +1372,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), new Case("n", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", -0.0d), + ev("r6", -0.0f))))); } @Test @@ -1089,11 +1399,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), new Case("n", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))))); } @Test @@ -1122,35 +1438,59 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("+0.25", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot25, f0dot25), List.of("r0", "r6")), + """.formatted(d0dot25, f0dot25), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), new Case("-0.25", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot25, fn0dot25), List.of("r0", "r6")), + """.formatted(dn0dot25, fn0dot25), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), new Case("+0.5", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), new Case("-0.5", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of("r0", "r6")), + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), new Case("+0.75", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot75, f0dot75), List.of("r0", "r6")), + """.formatted(d0dot75, f0dot75), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), new Case("-0.75", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn0dot75, fn0dot75), List.of("r0", "r6")), + """.formatted(dn0dot75, fn0dot75), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))), new Case("+1.0", """ r1 =0x%x; r7l =0x%x; - """.formatted(d1dot0, f1dot0), List.of("r0", "r6")), + """.formatted(d1dot0, f1dot0), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), new Case("-1.0", """ r1 =0x%x; r7l =0x%x; - """.formatted(dn1dot0, fn1dot0), List.of("r0", "r6")))); + """.formatted(dn1dot0, fn1dot0), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))))); } @Test @@ -1165,7 +1505,10 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1l =0x%x; r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of("r0", "r6")))); + """.formatted(f0dot5, d0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))))); } @Test @@ -1181,7 +1524,10 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1l =1; r7 =2; - """, List.of("r0", "r6")))); + """, + List.of( + ev("r0", 1.0d), + ev("r6", 2.0f))))); } @Test @@ -1204,19 +1550,34 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4l =0x%x; r7 =0x%x; r10l=0x%x; - """.formatted(d1dot0, f1dot0, d1dot0, f1dot0), List.of("r0", "r6")), + """.formatted(d1dot0, f1dot0, d1dot0, f1dot0), + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), new Case("+0.5", """ r1 =0x%x; r4l =0x%x; r7 =0x%x; r10l=0x%x; - """.formatted(d0dot5, f0dot5, d0dot5, f0dot5), List.of("r0", "r6")), + """.formatted(d0dot5, f0dot5, d0dot5, f0dot5), + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), new Case("-0.5", """ r1 =0x%x; r4l =0x%x; r7 =0x%x; r10l=0x%x; - """.formatted(dn0dot5, dn0dot5, dn0dot5, fn0dot5), List.of("r0", "r6")))); + """.formatted(dn0dot5, dn0dot5, dn0dot5, fn0dot5), + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))))); } @Test @@ -1236,11 +1597,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("num", """ r1l =0x%x; r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of("r0", "r6")), + """.formatted(f0dot5, d0dot5), + List.of( + ev("r0", "0"), + ev("r6", "0"))), new Case("nan", """ r1l =0x%x; r7 =0x%x; - """.formatted(fNaN, dNaN), List.of("r0", "r6")))); + """.formatted(fNaN, dNaN), + List.of( + ev("r0", "1"), + ev("r6", "1"))))); } @Test @@ -1253,9 +1620,12 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( new Case("num", """ - r1l =0x%x; - r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of("r0", "r6")))); + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", -0.5d), + ev("r6l", -0.5f))))); } @Test @@ -1273,7 +1643,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 0.75d), + ev("r9", 0.75f))))); } @Test @@ -1291,7 +1663,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 0.25d), + ev("r9", 0.25f))))); } @Test @@ -1309,7 +1683,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 0.125d), + ev("r9", 0.125f))))); } @Test @@ -1327,7 +1703,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 2.0d), + ev("r9", 2.0f))))); } @Test @@ -1345,17 +1723,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", "0"), + ev("r9", "0"))))); } @Test @@ -1373,17 +1757,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", "1"), + ev("r9", "1"))))); } @Test @@ -1401,17 +1791,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", "0"), + ev("r9", "0"))))); } @Test @@ -1429,51 +1825,115 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of("r0", "r9")), + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", "0"), + ev("r9", "0"))))); } @Test public void testInt2CompOpGen() throws Exception { runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = -r1; + r0 = -r1; r6l = -r7l; """), List.of( new Case("pos", """ - r1l =4; - r7 =4; - """, List.of("r0", "r6")), + r1 =4; + r7l =4; + """, + List.of( + ev("r0", "-4"), + ev("r6l", "-4"))), new Case("neg", """ - r1l =-4; - r7 =-4; - """, List.of("r0", "r6")))); + r1 =-4; + r7l =-4; + """, + List.of( + ev("r0", "4"), + ev("r6l", "4"))))); + } + + @Test + public void testInt2CompMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp0:9 = -temp1; + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1 = 4; + """, + List.of( + ev("r0", "-4"), + ev("r2", "-1"))), + new Case("neg", """ + r1 =-4; + """, + List.of( + ev("r0", "4"), + ev("r2", "0"))))); } @Test public void testIntNegateOpGen() throws Exception { runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = ~r1; + r0 = ~r1; r6l = ~r7l; """), List.of( new Case("pos", """ - r1l =4; - r7 =4; - """, List.of("r0", "r6")), + r1 =4; + r7l =4; + """, + List.of( + ev("r0", "-5"), + ev("r6l", "-5"))), new Case("neg", """ - r1l =-4; - r7 =-4; - """, List.of("r0", "r6")))); + r1 =-4; + r7l =-4; + """, + List.of( + ev("r0", "3"), + ev("r6l", "3"))))); + } + + @Test + public void testIntNegateMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp0:9 = ~temp1; + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1 = 4; + """, + List.of( + ev("r0", "-5"), + ev("r2", "-1"))), + new Case("neg", """ + r1 = -4; + """, + List.of( + ev("r0", "3"), + ev("r2", "0"))))); } @Test @@ -1484,10 +1944,36 @@ public class JitCodeGeneratorTest extends AbstractJitTest { List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + """, + List.of( + ev("r0", "4"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + """, + List.of( + ev("r0", "-4"))))); + } + + @Test + public void testIntSExtMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:9 = sext(r1l); + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "4"), + ev("r2", "0"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "-4"), + ev("r2", "-1"))))); } @Test @@ -1498,10 +1984,36 @@ public class JitCodeGeneratorTest extends AbstractJitTest { List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + """, + List.of( + ev("r0", "4"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + """, + List.of( + ev("r0", "0xfffffffc"))))); + } + + @Test + public void testIntZExtMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:9 = zext(r1l); + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "4"), + ev("r2", "0"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "0xfffffffc"), + ev("r2", "0xffffff"))))); } @Test @@ -1509,14 +2021,48 @@ public class JitCodeGeneratorTest extends AbstractJitTest { // Test size change, even though not necessary here runEquivalenceTest(translateSleigh(ID_TOYBE64, """ r0 = lzcount(r1l); + + temp:3 = r3(0); + r2 = lzcount(temp); """), List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + r3 =4; + """, + List.of( + ev("r0", "29"), + ev("r2", "21"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + r3 =-4; + """, + List.of( + ev("r0", "0"), + ev("r2", "0"))))); + } + + @Test + public void testLzCountMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1s:9 = sext(r1); + temp1z:9 = zext(r1); + r0 = lzcount(temp1s); + r2 = lzcount(temp1z); + """), + List.of( + new Case("pos", """ + r1 =4; + """, + List.of( + ev("r0", "69"), + ev("r2", "69"))), + new Case("neg", """ + r1 =-4; + """, + List.of( + ev("r0", "0"), + ev("r2", "8"))))); } @Test @@ -1528,10 +2074,37 @@ public class JitCodeGeneratorTest extends AbstractJitTest { List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + """, + List.of( + ev("r0", "1"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + """, + List.of( + ev("r0", "30"))))); + } + + @Test + public void testPopCountMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1s:9 = sext(r1); + temp1z:9 = zext(r1); + r0 = popcount(temp1s); + r2 = popcount(temp1z); + """), + List.of( + new Case("pos", """ + r1 =4; + """, + List.of( + ev("r0", "1"), + ev("r2", "1"))), + new Case("neg", """ + r1 =-4; + """, + List.of( + ev("r0", "70"), + ev("r2", "62"))))); } @Test @@ -1544,7 +2117,164 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x%x; r4l=0x12345678; - """.formatted(LONG_CONST), List.of("r0")))); + """.formatted(LONG_CONST), + List.of( + ev("r0l", "0xadbeefca"), + ev("r3", "0x12"))))); + } + + @Test + public void testSubPieceMpIntConst9_0() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:9 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst9_1() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:9 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst10_0() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:10 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst10_1() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:10 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst10_2() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:10 = 0x1122334455667788; + r0 = temp0(2); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x112233445566"))))); + } + + @Test + public void testSubPieceMpIntConst11_0() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:11 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst11_1() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:11 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst11_2() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:11 = 0x1122334455667788; + r0 = temp0(2); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x112233445566"))))); + } + + @Test + public void testSubPieceMpIntConst11_3() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:11 = 0x1122334455667788; + r0 = temp0(3); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455"))))); + } + + @Test + public void testSubPieceMpIntConst12_0() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:12 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst12_1() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:12 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst12_2() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:12 = 0x1122334455667788; + r0 = temp0(2); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x112233445566"))))); + } + + @Test + public void testSubPieceMpIntConst12_3() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:12 = 0x1122334455667788; + r0 = temp0(3); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455"))))); + } + + @Test + public void testSubPieceMpIntConst12_4() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:12 = 0x1122334455667788; + r0 = temp0(4); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344"))))); } @Test @@ -1557,7 +2287,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =2; r2 =2; r10l=2; r11l=2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "4"), + ev("r9", "4"))))); + } + + @Test + public void testIntAddMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 + temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "4"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x87654323456789a"))))); } @Test @@ -1570,7 +2324,34 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =2; r2 =2; r10l=2; r11l=2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSubMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 - temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0xf9abcdf00fedcbaa"), + ev("r3", "0xfff9abcdf00fedcb"))))); } @Test @@ -1583,7 +2364,32 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =2; r2 =2; r10l=2; r11l=2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "4"), + ev("r9", "4"))))); + } + + @Test + public void testIntMultMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp0:16 = zext(r1) * zext(r2); + r0 = temp0[0,64]; + r3 = temp0[64,64]; + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 7; + """, + List.of( + ev("r0", "14"), + ev("r3", "0"))), + new Case("large", """ + r1 = 0xffeeddccbbaa9988; r2 = 0x8877665544332211; + """, + List.of( + ev("r0", "0x30fdc971d4d04208"), + ev("r3", "0x886e442c48bba72d"))))); } @Test @@ -1596,19 +2402,116 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "2"), + ev("r9", "2"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0x7ffffffffffffffd"), + ev("r9", "0x7ffffffd"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntDivOpGenWith3ByteOperand() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp:3 = r1 + r2; + r0 = temp / r0; + """), + List.of( + new Case("only", """ + r1 = 0xdead; + r2 = 0xbeef; + r0 = 4; + """, + List.of( + ev("r0", "0x6767"))))); + } + + @Test + public void testIntDivMpIntOpGenNonUniform() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + r0l = temp1 / r2; + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x2ee95b10"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x00000000"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x0e658826"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x000000ff"))))); + } + + @Test + public void testIntDivMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local quotient = temp1 / temp2; + r0l = quotient(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x2ee95b10"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x00000000"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x0e658826"))), + // NOTE: Result differs from NonUniform, because r2 is also sext()ed + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x00000000"))))); } @Test @@ -1621,19 +2524,66 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "2"), + ev("r9l", "2"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "-2"), + ev("r9l", "-2"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "-2"), + ev("r9l", "-2"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "2"), + ev("r9l", "2"))))); + } + + @Test + public void testIntSDivMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local quotient = temp1 s/ temp2; + r0l = quotient(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x2ee95b10"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0xd116a4f0"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0xd116a4f0"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0x2ee95b10"))))); } @Test @@ -1646,19 +2596,66 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "5"), + ev("r9l", "5"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "-5"), + ev("r9l", "-5"))))); + } + + @Test + public void testIntRemMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local remainder = temp1 % temp2; + r0l = remainder(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x0c49"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0xefcdab89"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x00bf"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0x10325477"))))); } @Test @@ -1671,19 +2668,66 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "-1"), + ev("r9l", "-1"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "-1"), + ev("r9l", "-1"))))); + } + + @Test + public void testIntSRemMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local quotient = temp1 s% temp2; + r0l = quotient(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x0c49"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0x0c49"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0xfffff3b7"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0xfffff3b7"))))); } @Test @@ -1696,7 +2740,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x3; r2 =0x5; r10l=0x3; r11l=0x5; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntAndMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 & temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "2"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x8101010102200220"))))); } @Test @@ -1709,7 +2777,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x3; r2 =0x5; r10l=0x3; r11l=0x5; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "7"), + ev("r9", "7"))))); + } + + @Test + public void testIntOrMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 | temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "2"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x877553313236767a"))))); } @Test @@ -1722,7 +2814,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x3; r2 =0x5; r10l=0x3; r11l=0x5; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "6"), + ev("r9", "6"))))); + } + + @Test + public void testIntXorMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 ^ temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "0"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x67452303016745a"))))); } @Test @@ -1735,23 +2851,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 == temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "0"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "0"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "0"))))); } @Test @@ -1764,23 +2930,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntNotEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 != temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "1"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "1"))))); } @Test @@ -1793,23 +3009,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntLessEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 <= temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "0"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "1"))))); } @Test @@ -1822,23 +3088,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSLessEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 s<= temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "1"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "0"))))); } @Test @@ -1851,23 +3167,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntLessMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 < temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "0"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "1"))))); } @Test @@ -1880,23 +3246,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSLessMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 s< temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "1"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "0"))))); } @Test @@ -1907,30 +3323,86 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( new Case("f", """ - r1 =0x1000000000000000; r2 =0x0100000000000000; - r10l=0x10000000; r11l=0x01000000; - """, List.of("r0", "r9")), + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("t", """ - r1 =0x1000000000000000; r2 =0x1000000000000000; - r10l=0x10000000; r11l=0x10000000; - """, List.of("r0", "r9")))); + r1 =0x8000000000000000; r2 =0x8000000000000000; + r10l=0x80000000; r11l=0x80000000; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntCarryMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1) << 8; + temp2:9 = zext(r2) << 8; + r0 = carry(temp1, temp2); + """), + List.of( + new Case("f", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"))), + new Case("t", """ + r1 =0x8000000000000000; r2 =0x8000000000000000; + r10l=0x80000000; r11l=0x80000000; + """, + List.of( + ev("r0", "1"))))); } @Test public void testIntSCarryOpGen() throws Exception { runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = carry(r1, r2); - r9l = carry(r10l, r11l); + r0 = scarry(r1, r2); + r9l = scarry(r10l, r11l); """), List.of( new Case("f", """ - r1 =0x1000000000000000; r2 =0x0100000000000000; - r10l=0x10000000; r11l=0x01000000; - """, List.of("r0", "r9")), + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("t", """ - r1 =0x0100000000000000; r2 =0x0100000000000000; - r10l=0x01000000; r11l=0x01000000; - """, List.of("r0", "r9")))); + r1 =0x4000000000000000; r2 =0x4000000000000000; + r10l=0x40000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntSCarryMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1) << 8; + temp2:9 = zext(r2) << 8; + r0 = scarry(temp1, temp2); + """), + List.of( + new Case("f", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"))), + new Case("t", """ + r1 =0x4000000000000000; r2 =0x4000000000000000; + r10l=0x40000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"))))); } @Test @@ -1940,14 +3412,42 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r9l = sborrow(r10l, r11l); """), List.of( - new Case("f", """ - r1 =0x1000000000000000; r2 =0x0100000000000000; - r10l=0x10000000; r11l=0x01000000; - """, List.of("r0", "r9")), new Case("t", """ - r1 =0x1100000000000000; r2 =0x0100000000000000; - r10l=0x11000000; r11l=0x01000000; - """, List.of("r0", "r9")))); + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("f", """ + r1 =0xc000000000000000; r2 =0x4000000000000000; + r10l=0xc0000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSBorrowMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = zext(r1) << 8; + temp2:9 = zext(r2) << 8; + r0 = sborrow(temp1, temp2); + """), + List.of( + new Case("t", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"))), + new Case("f", """ + r1 =0xc000000000000000; r2 =0x4000000000000000; + r10l=0xc0000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"))))); } @Test @@ -1964,31 +3464,116 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =100; r5l =4; r7l =100; r8 =4; r10l=100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0x640"), + ev("r3", "0x640"), + ev("r6l", "0x640"), + ev("r9l", "0x640"))), new Case("posLbigR", """ r1 =100; r2 =0x100000004; r4 =100; r5l =0x100000004; r7l =100; r8 =0x100000004; r10l=100; r11l=0x100000004; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0x640"), + ev("r6l", "0"), + ev("r9l", "0x640"))), new Case("posLnegR", """ r1 =100; r2 =-4; r4 =100; r5l =-4; r7l =100; r8 =-4; r10l=100; r11l=-4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))), new Case("negLposR", """ r1 =-100; r2 =4; r4 =-100; r5l =4; r7l =-100; r8 =4; r10l=-100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "-0x640"), + ev("r3", "-0x640"), + ev("r6l", "-0x640"), + ev("r9l", "-0x640"))), new Case("negLnegR", """ r1 =-100; r2 =-4; r4 =-100; r5l =-4; r7l =-100; r8 =-4; r10l=-100; r11l=-4; - """, List.of("r0", "r3", "r6", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))))); + } + + @Test + public void testIntLeftMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = (zext(r2) << 64) + r3; + temp0:9 = temp1 << temp2; + r0 = temp0(0); + r4 = temp0(1); + """), + List.of( + new Case("posLposR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xedcba98765432100"), + ev("r4", "0x07edcba987654321"))), + new Case("posLmedR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x6543210000000000"), + ev("r4", "0x8765432100000000"))), + new Case("posLbigR", """ + r1 = 0x7edcba9876543210; + r2 = 0x40; + r3 = 4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("posLnegR", """ + r1 = 0x7edcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("negLposR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xedcba98765432100"), + ev("r4", "0xffedcba987654321"))), + new Case("negLnegR", """ + r1 = 0xfedcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))))); } @Test @@ -2005,31 +3590,124 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =100; r5l =4; r7l =100; r8 =4; r10l=100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "6"), + ev("r3", "6"), + ev("r6l", "6"), + ev("r9l", "6"))), new Case("posLbigR", """ r1 =100; r2 =0x100000004; r4 =100; r5l =0x100000004; r7l =100; r8 =0x100000004; r10l=100; r11l=0x100000004; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "6"), + ev("r6l", "0"), + ev("r9l", "6"))), new Case("posLnegR", """ r1 =100; r2 =-4; r4 =100; r5l =-4; r7l =100; r8 =-4; r10l=100; r11l=-4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))), new Case("negLposR", """ r1 =-100; r2 =4; r4 =-100; r5l =4; r7l =-100; r8 =4; r10l=-100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0x0ffffffffffffff9"), + ev("r3", "0x0ffffffffffffff9"), + ev("r6l", "0x0ffffff9"), + ev("r9l", "0x0ffffff9"))), new Case("negLnegR", """ r1 =-100; r2 =-4; r4 =-100; r5l =-4; r7l =-100; r8 =-4; r10l=-100; r11l=-4; - """, List.of("r0", "r3", "r6", "r9")))); + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))))); + } + + @Test + public void testIntRightMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = (zext(r2) << 64) + r3; + temp0:9 = temp1 >> temp2; + r0 = temp0(0); + r4 = temp0(1); + """), + List.of( + new Case("posLposR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0x07edcba987654321"), + ev("r4", "0x0007edcba9876543"))), + new Case("posLmedR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x0000000007edcba9"), + ev("r4", "0x000000000007edcb"))), + new Case("posLbigR", """ + r1 = 0x7edcba9876543210; + r2 = 0x40; + r3 = 4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("posLnegR", """ + r1 = 0x7edcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("negLposR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xffedcba987654321"), + ev("r4", "0x0fffedcba9876543"))), + new Case("negLmedR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x0000000fffedcba9"), + ev("r4", "0x000000000fffedcb"))), + new Case("negLnegR", """ + r1 = 0xfedcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))))); } @Test @@ -2046,31 +3724,148 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =100; r5l =4; r7l =100; r8 =4; r10l=100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "6"), + ev("r3", "6"), + ev("r6l", "6"), + ev("r9l", "6"))), new Case("posLbigR", """ r1 =100; r2 =0x100000004; r4 =100; r5l =0x100000004; r7l =100; r8 =0x100000004; r10l=100; r11l=0x100000004; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "6"), + ev("r6l", "0"), + ev("r9l", "6"))), new Case("posLnegR", """ r1 =100; r2 =-4; r4 =100; r5l =-4; r7l =100; r8 =-4; r10l=100; r11l=-4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))), new Case("negLposR", """ r1 =-100; r2 =4; r4 =-100; r5l =4; r7l =-100; r8 =4; r10l=-100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + List.of( + ev("r0", "-7"), + ev("r3", "-7"), + ev("r6l", "-7"), + ev("r9l", "-7"))), new Case("negLnegR", """ r1 =-100; r2 =-4; r4 =-100; r5l =-4; r7l =-100; r8 =-4; r10l=-100; r11l=-4; - """, List.of("r0", "r3", "r6", "r9")))); + """, + List.of( + ev("r0", "-1"), + ev("r3", "-1"), + ev("r6l", "-1"), + ev("r9l", "-1"))))); + } + + @Test + public void testIntSRight3IntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:3 = r1(0); + temp0:3 = temp1 s>> r2; + r0 = zext(temp0); + """), + List.of( + new Case("posLposR", """ + r1 = 0xfedcba; + r2 = 4; + """, + List.of( + ev("r0", "0xffedcb"))))); + } + + @Test + public void testIntSRightMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(ID_TOYBE64, """ + temp1:9 = sext(r1); + temp2:9 = (zext(r2) << 64) + r3; + temp0:9 = temp1 s>> temp2; + r0 = temp0(0); + r4 = temp0(1); + """), + List.of( + new Case("posLposR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0x07edcba987654321"), + ev("r4", "0x0007edcba9876543"))), + new Case("posLmedR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x0000000007edcba9"), + ev("r4", "0x000000000007edcb"))), + new Case("posLbigR", """ + r1 = 0x7edcba9876543210; + r2 = 0x40; + r3 = 4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("posLnegR", """ + r1 = 0x7edcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("negLposR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xffedcba987654321"), + ev("r4", "0xffffedcba9876543"))), + new Case("negLlegR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 32; + """, + List.of( + ev("r0", "0xfffffffffedcba98"), + ev("r4", "0xfffffffffffedcba"))), + new Case("negLmedR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0xffffffffffedcba9"), + ev("r4", "0xffffffffffffedcb"))), + new Case("negLnegR", """ + r1 = 0xfedcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "-1"), + ev("r4", "-1"))))); } @Test diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/tgt/JitRuntimeLibraryTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/tgt/JitRuntimeLibraryTest.java new file mode 100644 index 0000000000..8f10b58460 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/tgt/JitRuntimeLibraryTest.java @@ -0,0 +1,122 @@ +/* ### + * 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.pcode.emu.jit.gen.tgt; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.commons.lang3.ArrayUtils; +import org.junit.Test; + +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.MpDivPrivate; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.MpShiftPrivate; + +public class JitRuntimeLibraryTest { + + int[] intsLE(int... legs) { + ArrayUtils.reverse(legs); + return legs; + } + + String mpToString(int[] legs) { + List strs = IntStream.of(legs).mapToObj(i -> "%08x".formatted(i)).toList(); + return strs.reversed().stream().collect(Collectors.joining(":")); + } + + void assertMpEquals(int[] expected, int[] actual) { + assertEquals(mpToString(expected), mpToString(actual)); + } + + int[] out(int size, Consumer func) { + int[] out = new int[size]; + func.accept(out); + return out; + } + + @Test + public void testMpShiftPrivateShl() { + assertMpEquals(intsLE(0x89abcdef, 0xfedcba98, 0x76543210, 0x00000000), + out(4, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 32))); + assertMpEquals(intsLE(0x9abcdeff, 0xedcba987, 0x65432100, 0x00000000), + out(4, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0xedcba987, 0x65432100, 0x00000000), + out(3, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0x00000000, 0x9abcdeff, 0xedcba987, 0x65432100, 0x00000000), + out(5, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + } + + @Test + public void testMpShiftPrivateUshr() { + assertMpEquals(intsLE(0x00000000, 0x01234567, 0x89abcdef, 0xfedcba98), + out(4, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 32))); + assertMpEquals(intsLE(0x00000000, 0x00123456, 0x789abcde, 0xffedcba9), + out(4, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0x00123456, 0x789abcde, 0xffedcba9), + out(3, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0x00000000, 0x00000000, 0x00123456, 0x789abcde, 0xffedcba9), + out(5, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + } + + @Test + public void testMpDivPrivateLz() { + assertEquals(0, MpDivPrivate.lz(intsLE(-1))); + assertEquals(1, MpDivPrivate.lz(intsLE(Integer.MAX_VALUE))); + assertEquals(64, MpDivPrivate.lz(intsLE(0, 0, -1))); + } + + @Test + public void testMpDivPrivateShl() { + int[] ints = intsLE(0xffffffff, 0x12345678); + MpDivPrivate.shl(ints, 4); + assertMpEquals(intsLE(0xfffffff1, 0x23456780), ints); + } + + @Test + public void testMpDivPrivateShr() { + int[] ints = intsLE(0x12345678, 0xffffffff); + MpDivPrivate.shr(ints, 4); + assertMpEquals(intsLE(0x01234567, 0x8fffffff), ints); + } + + @Test + public void testMpDivPrivateNeg() { + int[] ints; + + ints = intsLE(0x00000000, 0x00000000); + MpDivPrivate.neg(ints, 2); + assertMpEquals(intsLE(0x00000000, 0x00000000), ints); + + ints = intsLE(0x80000000, 0x00000000); + MpDivPrivate.neg(ints, 2); + assertMpEquals(intsLE(0x80000000, 0x00000000), ints); + + ints = intsLE(0xffffffff, 0xffff0000); + MpDivPrivate.neg(ints, 2); + assertMpEquals(intsLE(0x00000000, 0x00010000), ints); + } +}