Compare commits

...

34 commits

Author SHA1 Message Date
Ryan Kurtz
970c8fbdd5 Merge remote-tracking branch 'origin/patch' 2025-09-11 05:13:10 -04:00
LucaPalumbo
bc6701a24d Fix off-by-one in ElfHeader.getSectionLoadHeaderContaining
(Closes #8440)
2025-09-11 05:10:48 -04:00
Ryan Kurtz
e330fc2077 Merge remote-tracking branch 'origin/GP-0-dragonmacher-test-fixes-9-10-25' 2025-09-10 14:20:49 -04:00
dragonmacher
6bba2ea685 Test fixes 2025-09-10 14:14:21 -04:00
Ryan Kurtz
2993827690 Merge remote-tracking branch
'origin/GP-5970-dragonmacher-scirpting-output-color--SQUASHED'
(Closes #2849)
2025-09-10 14:14:00 -04:00
Ryan Kurtz
59bcbba8cf Merge remote-tracking branch 'origin/GP-5978-dragonmacher-alt-graph-fix'
(Closes #8205)
2025-09-10 14:09:50 -04:00
Ryan Kurtz
272314d7af Merge remote-tracking branch 'origin/GP-5214_Dan_jitMpInt--SQUASHED' 2025-09-10 14:01:21 -04:00
Ryan Kurtz
eadb23c45a Merge remote-tracking branch
'origin/GP-5982-dragonmacher-symbol-tree-node-equals-npe' (Closes #8502)
2025-09-10 13:56:33 -04:00
dragonmacher
89b6306aea GP-5982 - Symbol Tree - NPE fix 2025-09-10 12:43:49 -04:00
Ryan Kurtz
657ec39201 GP-5637: More improvements to the PyGhidra API 2025-09-10 12:39:57 -04:00
Dan
df90de2367 GP-5214: Add support for multi-precision integer operations in the JIT-accelerated emulator. 2025-09-10 16:32:13 +00:00
dragonmacher
73bdee2546 GP-5970 - Scripting - A print/println methods to color console output 2025-09-10 12:21:42 -04:00
Ryan Kurtz
5fbb052b28 Merge remote-tracking branch
'origin/GP-5964-dragonmacher-file-chooser-accessibility--SQUASHED'
(Closes #6310, Closes #7129, Closes #7130)
2025-09-10 05:47:31 -04:00
Ryan Kurtz
4f164e68a6 Merge branch 'GP-5969_VxWorksTableIDFix' 2025-09-10 05:26:33 -04:00
emteere
6d588dab00 GP-5969: Updated vxWorks symbol table finding script to better filter
out runs of pointers to locate the table, deleted old scripts, and fixed
de-mangling and labeling issues.
2025-09-10 05:25:47 -04:00
dragonmacher
f781273d49 Removed Alt to Alt Graph mapping, since the original bug has been fixed
in Java
2025-09-09 18:00:23 -04:00
dragonmacher
dd6561807b GP-5964 - File Chooser - Accessibility tweaks 2025-09-09 15:22:03 -04:00
Ryan Kurtz
58ca21b8db Merge remote-tracking branch 'origin/GP-5973_Dan_lldbsetupTypos'
(Closes #8498)
2025-09-09 13:58:40 -04:00
Ryan Kurtz
e5684417c0 Merge remote-tracking branch 'origin/GP-0_Dan_z3ExtProps' 2025-09-09 12:12:36 -04:00
Ryan Kurtz
fca6104af0 Merge remote-tracking branch 'origin/GP-5566_d-millar_lisa_SQUASH250908' 2025-09-09 11:45:24 -04:00
Ryan Kurtz
b5e91aeac0 Merge remote-tracking branch 'origin/patch' 2025-09-09 11:24:08 -04:00
Ryan Kurtz
0613d364fc Merge remote-tracking branch 'origin/GP-0-dragonmacher-test-deadlock-fix-v3' into patch 2025-09-09 11:20:41 -04:00
dragonmacher
deddc84205 Fix for previous commit; backport of master fix 2025-09-09 11:18:24 -04:00
Dan
20314d087d GP-5973: Fix typos in lldbsetuputils.ps1 2025-09-09 15:12:19 +00:00
Dan
0645a51808 GP-0: Add extension.properties for SymbolicSummaryZ3 2025-09-09 15:06:13 +00:00
Ryan Kurtz
221939c0a9 Merge remote-tracking branch 'origin/GP-0-dragonmacher-test-deadlock-fix' into patch 2025-09-09 10:45:35 -04:00
Ryan Kurtz
5769aec3f3 Merge remote-tracking branch
'origin/GP-5967-dragonmacher-key-binding-ui-improvement' (Closes #7024)
2025-09-09 07:57:39 -04:00
Ryan Kurtz
998bea8c50 Merge remote-tracking branch 'origin/GP-5883_ghidragon_memory_search_bug' 2025-09-09 07:55:31 -04:00
Ryan Kurtz
457d342887 Merge remote-tracking branch 'origin/GP-5933_ghidragon_update_mem_searrch_to_support_mutliple_patterns--SQUASHED' 2025-09-09 07:52:29 -04:00
ghidragon
93f92fa879 GP-5933 Added support for searching memory for multiple patters in the same pass through memory 2025-09-08 14:50:28 -04:00
ghidragon
8272662627 GP-5883 fixed Memory Search Options GUI not initializing all options to
current model settings
2025-09-08 13:10:02 -04:00
d-millar
f5572ede99 GP-5566: first/last? changes for ghidra
GP-5566: fix for J

GP-5566: minor fixes

GP-5566: icons decl proper

GP-5566: minor tweaks

GP-5566: post-review

GP-5566: minor

GP-5566: mo better monitor logic

GP-5566: minor fix
2025-09-08 15:36:25 +00:00
dragonmacher
48754fb98d Test deadlock fix 2025-09-06 16:30:31 -04:00
dragonmacher
d538513428 GP-5967 - Improved Options Key Binding UI 2025-09-04 17:02:11 -04:00
164 changed files with 6917 additions and 2170 deletions

View file

@ -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)

View file

@ -3,3 +3,4 @@
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/symbolic.summary.z3.theme.properties||GHIDRA||||END|
extension.properties||GHIDRA||||END|

View file

@ -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@

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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);
}
}
}

View file

@ -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<CodeCompletion> {
CodeCompletionListCellRenderer() {
setBaseFontId(CodeCompletionWindow.FONT_ID);
}
@Override
protected String getItemText(CodeCompletion value) {
return value.getDescription();
@ -421,7 +425,6 @@ class CodeCompletionListCellRenderer extends GListCellRenderer<CodeCompletion> {
}
component.setEnabled(list.isEnabled());
component.setFont(list.getFont());
component.setComponentOrientation(list.getComponentOrientation());
Border border = null;
if (cellHasFocus) {

View file

@ -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.

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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)}.
* <p>
* 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)}.
* <p>
* 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

View file

@ -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);
}
/**

View file

@ -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 &gt;= 0
*
* @throws UnsupportedOperationException
*/
public int getTextLength();
@ -128,8 +126,6 @@ public interface ConsoleService {
* @param length the length of the desired string &gt;= 0
*
* @return the text, in a String of length &gt;= 0
*
* @throws UnsupportedOperationException
*/
public String getText(int offset, int length);
}

View file

@ -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;
}
}

View file

@ -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");

View file

@ -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) {}
}

View file

@ -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<ByteMatcher> matchers;
public CombinedByteMatcher(List<ByteMatcher> matchers, SearchSettings settings) {
super("Multi-Pattern Matcher", null, settings);
this.matchers = matchers;
}
@Override
public Iterable<ByteMatch> match(ExtendedByteSequence bytes) {
return new MultiMatcherIterator(bytes);
}
@Override
public String getDescription() {
return getName();
}
@Override
public String getToolTip() {
return null;
}
private class MultiMatcherIterator implements Iterable<ByteMatch>, Iterator<ByteMatch> {
private Iterator<ByteMatcher> matcherIterator;
private Iterator<ByteMatch> 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<ByteMatch> 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<ByteMatch> iterator() {
return this;
}
}
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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();
}
}
}

View file

@ -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);
}

View file

@ -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());

View file

@ -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);

View file

@ -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<DockingActionIf> list = tool.getAllActions();
for (DockingActionIf action : list) {
if (ignoreAction(action)) {

View file

@ -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();

View file

@ -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<ByteMatch> 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<ByteMatch> 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<ByteMatch> results = match("There are no matches here!");
assertEquals(0, results.size());
}
private List<ByteMatch> match(String s) {
ExtendedByteSequence sequence = createByteSequence(s);
Iterable<ByteMatch> 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;
}
}

View file

@ -49,10 +49,10 @@ public class MaskedBytesSequenceByteMatcherTest {
Iterator<ByteMatch> 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<ByteMatch> 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<ByteMatch> 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());
}

View file

@ -45,10 +45,10 @@ public class RegExByteMatcherTest {
Iterator<ByteMatch> 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<ByteMatch> 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());
}

View file

@ -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|

View file

@ -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]

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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.");

View file

@ -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.

View file

@ -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;
}
}
}

View file

@ -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<TaintLabel> 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<Address, Set<TaintQueryResult>> vmap, TaskType delta);

View file

@ -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.");
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -216,6 +216,7 @@ public class SarifTaintResultHandler extends SarifResultHandler {
protected void doRun(TaskMonitor monitor) {
int[] selected = tableProvider.filterTable.getTable().getSelectedRows();
Map<Address, Set<TaintQueryResult>> map = new HashMap<>();
AddressSet set = new AddressSet();
for (int row : selected) {
Map<String, Object> 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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

View file

@ -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<Symbol> 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<Symbol> 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<Symbol> 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
}
}
}
}

View file

@ -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<Symbol> 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<Symbol> 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<Symbol> 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
}
}
}
}

View file

@ -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<Symbol> 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;
}

View file

@ -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.
"""
```

View file

@ -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()

View file

@ -111,6 +111,10 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
public void showTable(String logName, SarifSchema210 sarif) {
SarifDataFrame df = new SarifDataFrame(sarif, this, false);
int size = df.getTableResults().size();
if (size != 0) {
logName += " ["+size+"]";
}
SarifResultsTableProvider provider =
new SarifResultsTableProvider(logName, getPlugin(), this, df);
provider.filterTable.addSelectionListener(this);
@ -278,7 +282,6 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
return defaultGraphHandler;
}
@SuppressWarnings("unchecked")
public void setDefaultGraphHander(Class<? extends SarifGraphRunHandler> clazz) {
defaultGraphHandler = clazz;
}

View file

@ -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<String, Object> result, Program program, AddressSet set)
throws AddressOverflowException {

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -191,6 +191,11 @@ public interface EmuSyscallLibrary<T> extends PcodeUseropLibrary<T> {
return false;
}
@Override
public Class<?> getOutputType() {
return void.class;
}
@Override
public PcodeUseropLibrary<?> getDefiningLibrary() {
return syslib;

View file

@ -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

View file

@ -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();
}
}

View file

@ -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());

View file

@ -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

View file

@ -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();
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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 = "<BR><HR><CENTER>(";
private static final String END_KEYBINDNIG_TEXT = ")</CENTER>";
@ -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,

View file

@ -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;

View file

@ -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.
* <p>
* 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(

View file

@ -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);
}
}
}

View file

@ -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;
}

View file

@ -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<GhidraFileFilter>) filterCombo.getModel();
addFileFilter(GhidraFileFilter.ALL);
filterCombo.addItemListener(e -> rescanCurrentDirectory());
@ -444,22 +492,12 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
return filenamePanel;
}
private class SelectionListener<T> implements DropDownSelectionChoiceListener<File> {
@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<File> 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<T> implements DropDownSelectionChoiceListener<File> {
@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();
}
}
}
}

View file

@ -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;

View file

@ -203,7 +203,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
}
private void cancelCurrentWorkerJob() {
if (worker != null && worker.isBusy()) {
if (worker != null) {
worker.clearAllJobsWithInterrupt_IKnowTheRisks();
}
}

View file

@ -145,6 +145,11 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
return false;
}
@Override
public Class<?> getOutputType() {
return void.class;
}
@Override
public Method getJavaMethod() {
return null;

View file

@ -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<MultiLocalSub> 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<MultiLocalPart> 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<JitVal, VarHandler> handlers = new HashMap<>();
private final Map<Varnode, VarHandler> handlersPerVarnode = new HashMap<>();
private final NavigableMap<Address, JvmLocal> locals = new TreeMap<>();
private final Deque<JvmTempAlloc> 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
*
* <p>
* This should be used by operator code generators <em>after</em> 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}.
* <p>
* This should be used by operator code generators <em>after</em> 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.
* <p>
* <b>NOTE:</b> 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<JvmLocal> genFreeLocals(String name, List<SimpleJitType> types,
private List<JvmLocal> genFreeLocals(String name, List<? extends SimpleJitType> types,
VarDesc desc) {
JvmLocal[] result = new JvmLocal[types.size()];
Iterable<SimpleJitType> it = language.isBigEndian()
? types
: () -> new ReverseListIterator<SimpleJitType>(types);
Iterable<? extends SimpleJitType> 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<Address, JvmLocal> leftEntry = locals.floorEntry(vn.getAddress());
assert overlapsLeft(leftEntry.getValue().vn, vn);
Address min = leftEntry.getKey();
NavigableMap<Address, JvmLocal> sub = locals.subMap(min, true, maxAddr(vn), true);
List<MultiLocalPart> 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<Varnode, MultiLocalPart> 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<Address, JvmLocal> firstEntry = locals.floorEntry(vn.getAddress());
assert overlapsLeft(firstEntry.getValue().vn, vn);
Address min = firstEntry.getKey();
NavigableMap<Address, JvmLocal> 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<Varnode, MultiLocalPart> 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<MultiLocalPart> parts = List.copyOf(legs.values());
return new MultiLocalVarHandler(switch (endian) {
case BIG -> parts;
case LITTLE -> parts.reversed();
}, type);
}
/**

View file

@ -79,6 +79,10 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
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 <em>varnode</em>.
*
@ -94,10 +98,14 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
* @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 <em>varnode</em>.
*
@ -113,7 +121,7 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
* @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);
}

View file

@ -224,30 +224,40 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
*/
protected List<JitVal> doGetDefinitions(NavigableMap<Long, JitVal> map, AddressSpace space,
long offset, int size) {
long end = offset + size;
List<JitVal> result = new ArrayList<>();
Entry<Long, JitVal> 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<Long, JitVal> 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;

View file

@ -152,17 +152,13 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
* Get the type behavior from the userop's Java method
*
* <p>
* 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<JitVal> {
}
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<JitVal> {
return decOp.canInlinePcode();
}
@Override
public Class<?> getOutputType() {
return decOp.getOutputType();
}
@Override
public Method getJavaMethod() {
return decOp.getJavaMethod();

View file

@ -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<IntJitType> legTypes() {
return List.of(this);
}
}
/**
@ -278,6 +288,11 @@ public interface JitType {
public LongJitType asInt() {
return this;
}
@Override
public List<LongJitType> legTypes() {
return List.of(this);
}
}
/**
@ -326,6 +341,11 @@ public interface JitType {
public IntJitType asInt() {
return IntJitType.I4;
}
@Override
public List<FloatJitType> legTypes() {
return List.of(this);
}
}
/**
@ -374,10 +394,20 @@ public interface JitType {
public LongJitType asInt() {
return LongJitType.I8;
}
@Override
public List<DoubleJitType> legTypes() {
return List.of(this);
}
}
/**
* <b>WIP</b>: The p-code types for integers of size 9 and greater.
* The p-code types for integers of size 9 and greater.
*
* <p>
* 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
*
* <p>
* 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<SimpleJitType> legTypes() {
@Override
public List<IntJitType> 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<SimpleJitType> 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
*
* <p>
* 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<? extends SimpleJitType> legTypes();
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -128,6 +128,11 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary<Object> {
return rtOp.canInlinePcode();
}
@Override
public Class<?> getOutputType() {
return rtOp.getOutputType();
}
@Override
public Method getJavaMethod() {
return rtOp.getJavaMethod();

View file

@ -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 =

View file

@ -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);
}

View file

@ -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<T extends JitBinOp> extends OpGen<T> {
/**
* 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:
*
* <pre>
* void method(int[] out, int[] inL, int[] inR);
* </pre>
*
* <p>
* 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
* <p>
* 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<T extends JitBinOp> extends OpGen<T> {
*/
@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);
}
}

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface BitwiseBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
public interface BitwiseBinOpGen<T extends JitBinOp> extends IntBinOpGen<T> {
@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<T extends JitBinOp> extends BinOpGen<T> {
int longOpcode();
/**
* <b>WIP</b>: 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<T extends JitBinOp> extends BinOpGen<T> {
*/
// [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<T extends JitBinOp> extends BinOpGen<T> {
@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<T extends JitBinOp> extends BinOpGen<T> {
case MpIntJitType t -> TODO("MpInt of differing sizes");
default -> throw new AssertionError();
}
return lType;
return rType;
}
}

View file

@ -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<JitBoolAndOp> {
/** The generator singleton */

View file

@ -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<JitBoolNegateOp> {
/** 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<JitBoolNegateOp> {
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;

View file

@ -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<JitBranchIndOp> {
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);

View file

@ -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<JitCBranchOp> {
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);

View file

@ -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<JitCallOtherOpIf> {
transition.generateInv(rv);
}
static Parameter findOutputParameter(Parameter[] parameters, Method method) {
List<Parameter> 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<JitCallOtherOpIf> {
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<JitCallOtherOpIf> {
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<AutoCloseable> 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 extends AutoCloseable> T add(T resource) {
resources.add(resource);
return resource;
}
}

View file

@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatTestOp;
*
* @param <T> the class of p-code op node in the use-def graph
*/
public interface CompareFloatOpGen<T extends JitFloatTestOp> extends BinOpGen<T> {
public interface CompareFloatOpGen<T extends JitFloatTestOp> extends FloatBinOpGen<T> {
/**
* The JVM opcode to perform the comparison with float operands on the stack.

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T> {
public interface CompareIntBinOpGen<T extends JitIntTestOp> extends IntBinOpGen<T> {
/**
* Whether the comparison of p-code integers is signed
*
* {@inheritDoc}
* <p>
* 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<T extends JitIntTestOp> extends BinOpGen<T>
*
* @return true if signed, false if not
*/
@Override
boolean isSigned();
/**
@ -58,6 +58,11 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
*/
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<T extends JitIntTestOp> extends BinOpGen<T>
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<T extends JitIntTestOp> extends BinOpGen<T>
/**
* The JVM opcode to perform the conditional jump for unsigned or long integers.
*
* <p>
* This is emitted <em>after</em> the application of {@link #LCMP} or the comparator method.
*
* @return the opcode
@ -104,7 +108,34 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
@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<T extends JitIntTestOp> extends BinOpGen<T>
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()));

View file

@ -34,6 +34,11 @@ public enum CopyOpGen implements UnOpGen<JitCopyOp> {
/** The generator singleton */
GEN;
@Override
public boolean isSigned() {
return false;
}
@Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitCopyOp op, JitBlock block,
JitType uType, MethodVisitor rv) {

View file

@ -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<JitFloatAbsOp> {
public enum FloatAbsOpGen implements FloatUnOpGen<JitFloatAbsOp> {
/** The generator singleton */
GEN;

View file

@ -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<JitFloatAddOp> {
public enum FloatAddOpGen implements FloatBinOpGen<JitFloatAddOp> {
/** The generator singleton */
GEN;

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface FloatBinOpGen<T extends JitFloatBinOp> extends BinOpGen<T> {
@Override
default boolean isSigned() {
return false;
}
}

View file

@ -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<JitFloatCeilOp> {
public enum FloatCeilOpGen implements FloatUnOpGen<JitFloatCeilOp> {
/** The generator singleton */
GEN;

Some files were not shown because too many files have changed in this diff Show more