mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 01:39:21 +02:00
Compare commits
34 commits
c18c7a8f50
...
970c8fbdd5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
970c8fbdd5 | ||
![]() |
bc6701a24d | ||
![]() |
e330fc2077 | ||
![]() |
6bba2ea685 | ||
![]() |
2993827690 | ||
![]() |
59bcbba8cf | ||
![]() |
272314d7af | ||
![]() |
eadb23c45a | ||
![]() |
89b6306aea | ||
![]() |
657ec39201 | ||
![]() |
df90de2367 | ||
![]() |
73bdee2546 | ||
![]() |
5fbb052b28 | ||
![]() |
4f164e68a6 | ||
![]() |
6d588dab00 | ||
![]() |
f781273d49 | ||
![]() |
dd6561807b | ||
![]() |
58ca21b8db | ||
![]() |
e5684417c0 | ||
![]() |
fca6104af0 | ||
![]() |
b5e91aeac0 | ||
![]() |
0613d364fc | ||
![]() |
deddc84205 | ||
![]() |
20314d087d | ||
![]() |
0645a51808 | ||
![]() |
221939c0a9 | ||
![]() |
5769aec3f3 | ||
![]() |
998bea8c50 | ||
![]() |
457d342887 | ||
![]() |
93f92fa879 | ||
![]() |
8272662627 | ||
![]() |
f5572ede99 | ||
![]() |
48754fb98d | ||
![]() |
d538513428 |
164 changed files with 6917 additions and 2170 deletions
|
@ -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)
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
Module.manifest||GHIDRA||||END|
|
||||
README.md||GHIDRA||||END|
|
||||
data/symbolic.summary.z3.theme.properties||GHIDRA||||END|
|
||||
extension.properties||GHIDRA||||END|
|
||||
|
|
5
Ghidra/Extensions/SymbolicSummaryZ3/extension.properties
Normal file
5
Ghidra/Extensions/SymbolicSummaryZ3/extension.properties
Normal 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@
|
|
@ -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
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.analysis;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AutoAnalysisManagerListener {
|
||||
|
||||
public void analysisEnded(AutoAnalysisManager manager, boolean isCancelled);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
*/
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import ghidra.app.plugin.core.console.ConsolePlugin;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Generic console interface allowing any plugin to print
|
||||
* messages to console window.
|
||||
|
@ -109,8 +109,6 @@ public interface ConsoleService {
|
|||
* please throw {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @return number of characters >= 0
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
public int getTextLength();
|
||||
|
||||
|
@ -128,8 +126,6 @@ public interface ConsoleService {
|
|||
* @param length the length of the desired string >= 0
|
||||
*
|
||||
* @return the text, in a String of length >= 0
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
public String getText(int offset, int length);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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) {}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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]
|
|
@ -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();
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,9 +533,6 @@ 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();
|
||||
|
@ -568,15 +542,6 @@ public class VxWorksSymTab_Finder extends GhidraScript {
|
|||
printf("Possible symbol table at " + cursor + " has length error\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (debug) {
|
||||
printf("False-positive: String pointer table at %s, skipping\n",
|
||||
cursor.toString());
|
||||
}
|
||||
cursor = cursor.add(table_size);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cursor = cursor.add(4);
|
||||
}
|
||||
|
@ -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);
|
||||
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);
|
||||
}
|
||||
else {
|
||||
println("createFunction: Failed to create function");
|
||||
createLabel(symLoc, symName, true);
|
||||
applyDemangled(symLoc, symName, symDemangledName);
|
||||
}
|
||||
applyDemangled(symLoc, symName, symDemangledName);
|
||||
break;
|
||||
|
||||
default:
|
||||
createLabel(symLoc, symName, true, SourceType.IMPORTED);
|
||||
println("Invalid symType " + symType + " !");
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
```
|
||||
|
||||
|
|
|
@ -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_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
|
||||
else:
|
||||
mgr.waitForAnalysis(None, monitor); # waits for all analysis to complete
|
||||
if not monitor.isCancelled():
|
||||
monitor.cancel()
|
||||
GhidraProgramUtilities.markProgramAnalyzed(program)
|
||||
return mgr_log
|
||||
finally:
|
||||
GhidraScriptUtil.releaseBundleHostReference()
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
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(
|
||||
|
|
|
@ -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 Border defaultBorder;
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent me) {
|
||||
if (isSelected()) {
|
||||
private void updateBorderBasedOnState() {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
defaultBorder = getBorder();
|
||||
setBorder(RAISED_BORDER);
|
||||
inside = true;
|
||||
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 focusGained(FocusEvent e) {
|
||||
updateBorderBasedOnState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent me) {
|
||||
if (isSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
inside = false;
|
||||
restoreBorder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (isSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
setBorder(LOWERED_BORDER);
|
||||
public void focusLost(FocusEvent e) {
|
||||
updateBorderBasedOnState();
|
||||
fileChooser.updateShortcutPanel();
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
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
|
||||
}
|
||||
part.generateStoreCode(gen, this.type, rv);
|
||||
}
|
||||
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());
|
||||
}
|
||||
return new MultiLocalVarHandler(parts, JitTypeBehavior.INTEGER.type(vn.getSize()));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, firstIndex + i);
|
||||
mv.visitVarInsn(ILOAD, result.idx(i));
|
||||
// [lleg1,...,llegN:INT,rlegN:INT]
|
||||
mv.visitInsn(intOpcode());
|
||||
// [lleg1,...,olegN:INT]
|
||||
mv.visitVarInsn(ISTORE, firstIndex + i);
|
||||
mv.visitVarInsn(ISTORE, result.idx(i));
|
||||
// [lleg1,...]
|
||||
}
|
||||
|
||||
// Push it all back, in reverse order
|
||||
for (int i = 0; i < legCount; i++) {
|
||||
mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1);
|
||||
OpGen.generateMpLegsFromTemp(result, legCount, mv);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,17 +221,66 @@ 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 {
|
||||
TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), rv);
|
||||
outMpType = MpIntJitType.forSize(defOp.out().size());
|
||||
rv.visitLdcInsn(outMpType.legsAlloc());
|
||||
rv.visitIntInsn(NEWARRAY, T_INT);
|
||||
}
|
||||
rv.visitVarInsn(ASTORE, out.idx(0));
|
||||
}
|
||||
else {
|
||||
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);
|
||||
|
@ -204,13 +288,41 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
|
|||
Type.getMethodDescriptor(method), false);
|
||||
// [return?]
|
||||
rv.visitLabel(tryEnd);
|
||||
if (op instanceof JitCallOtherDefOp defOp) {
|
||||
gen.generateVarWriteCode(defOp.out(), JitType.forJavaType(method.getReturnType()));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class ResourceGroup implements AutoCloseable {
|
||||
private final List<AutoCloseable> resources = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
for (AutoCloseable r : resources) {
|
||||
r.close();
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends AutoCloseable> T add(T resource) {
|
||||
resources.add(resource);
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Direct invocation strategy is applicable (see class documentation)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue