GP-210 - Added new 'Copy Special' items to make copying from Ghidra to

Python and C++ easier
This commit is contained in:
dragonmacher 2020-11-18 17:21:00 -05:00
parent 00ef824dbd
commit ccedc6627a
15 changed files with 1024 additions and 416 deletions

View file

@ -165,6 +165,15 @@
<LI><B>Byte String (No Spaces)</B> <A name="ByteString_NoSpaces"></A> - This is the same as <LI><B>Byte String (No Spaces)</B> <A name="ByteString_NoSpaces"></A> - This is the same as
the Byte String format, except there are no delimiting spaces between bytes.</LI> the Byte String format, except there are no delimiting spaces between bytes.</LI>
<LI><B>Python Byte String</B> - Copies the bytes into the Python byte string format, for
example: <CODE>b'\x41\xec\x49\x89'</CODE>.</LI>
<LI><B>Python List String</B> - Copies the bytes into the Python list format, for
example: <CODE>[ 0x66, 0x2e, 0x0f, 0x1f, 0x84 ]</CODE>.</LI>
<LI><B>C Array String</B> - Copies the bytes into the C array format, for
example: <CODE>{ 0x66, 0x2e, 0x0f, 0x1f, 0x84 }</CODE>.</LI>
<LI><B>Address</B> - Copies the address at the top of the selection.</LI> <LI><B>Address</B> - Copies the address at the top of the selection.</LI>
</UL> </UL>
@ -208,6 +217,17 @@
<LI><B>Byte String (No Spaces)</B> - This is the same as the Byte String format, except <LI><B>Byte String (No Spaces)</B> - This is the same as the Byte String format, except
there are no delimiting spaces between bytes.</LI> there are no delimiting spaces between bytes.</LI>
<LI><B>Python Byte String</B> - Copies the bytes into the Python byte string format, for
example: <CODE>b'\x41\xec\x49\x89'</CODE>.</LI>
<LI><B>Python List String</B> - Copies the bytes into the Python list format, for
example: <CODE>[ 0x66, 0x2e, 0x0f, 0x1f, 0x84 ]</CODE>.</LI>
<LI><B>C Array String</B> - Copies the bytes into the C array format, for
example: <CODE>{ 0x66, 0x2e, 0x0f, 0x1f, 0x84 }</CODE>.</LI>
</UL> </UL>
<P>The Byte Viewer <B>Bytes In Memory</B> window can paste the following formats:</P> <P>The Byte Viewer <B>Bytes In Memory</B> window can paste the following formats:</P>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

View file

@ -377,7 +377,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
if (!clipboardService.isValidContext(context)) { if (!clipboardService.isValidContext(context)) {
return false; return false;
} }
return clipboardService.canCopy(); return clipboardService.canCopy();
} }
@ -413,7 +412,8 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
} }
Clipboard systemClipboard = getSystemClipboard(); Clipboard systemClipboard = getSystemClipboard();
return clipboardService.canPaste(getAvailableDataFlavors(systemClipboard)); DataFlavor[] flavors = getAvailableDataFlavors(systemClipboard);
return clipboardService.canPaste(flavors);
} }
@Override @Override
@ -444,7 +444,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
if (!clipboardService.isValidContext(context)) { if (!clipboardService.isValidContext(context)) {
return false; return false;
} }
return clipboardService.canCopySpecial(); return clipboardService.canCopySpecial();
} }

View file

@ -48,6 +48,7 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.*; import ghidra.program.util.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
public class CodeBrowserClipboardProvider extends ByteCopier public class CodeBrowserClipboardProvider extends ByteCopier
implements ClipboardContentProviderService { implements ClipboardContentProviderService {
@ -85,20 +86,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return list; return list;
} }
private static final List<ClipboardType> PASTE_TYPES = createPasteTypesList();
private static List<ClipboardType> createPasteTypesList() {
List<ClipboardType> list = new LinkedList<>();
list.add(LABELS_COMMENTS_TYPE);
list.add(LABELS_TYPE);
list.add(COMMENTS_TYPE);
list.add(BYTE_STRING_TYPE);
list.add(BYTE_STRING_NO_SPACE_TYPE);
return list;
}
protected boolean copyFromSelectionEnabled; protected boolean copyFromSelectionEnabled;
protected ComponentProvider componentProvider; protected ComponentProvider componentProvider;
private ListingModel model; private ListingModel model;
@ -157,10 +144,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
else if (element.equals(COMMENTS_TYPE.getFlavor())) { else if (element.equals(COMMENTS_TYPE.getFlavor())) {
return pasteLabelsComments(pasteData, false, true); return pasteLabelsComments(pasteData, false, true);
} }
else if (element.equals(BYTE_STRING_FLAVOR)) {
String data = (String) pasteData.getTransferData(BYTE_STRING_FLAVOR);
return pasteByteString(data);
}
else if (element.equals(LabelStringTransferable.labelStringFlavor)) { else if (element.equals(LabelStringTransferable.labelStringFlavor)) {
return pasteLabelString(pasteData); return pasteLabelString(pasteData);
} }
@ -169,10 +152,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
} }
} }
// last ditch effort, try to paste as a byte string if (super.pasteBytes(pasteData)) {
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor); return true;
if (string != null) {
return pasteByteString(string);
} }
tool.setStatusInfo("Paste failed: unsupported data type", true); tool.setStatusInfo("Paste failed: unsupported data type", true);
@ -181,36 +162,14 @@ public class CodeBrowserClipboardProvider extends ByteCopier
String msg = e.getMessage(); String msg = e.getMessage();
if (msg == null) { if (msg == null) {
msg = e.toString(); msg = e.toString();
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
} }
Msg.error(this, "Unexpected Exception: " + msg, e);
tool.setStatusInfo("Paste failed: " + msg, true); tool.setStatusInfo("Paste failed: " + msg, true);
} }
return false; return false;
} }
@Override
protected boolean supportsPasteTransferable(Transferable transferable) {
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for (DataFlavor element : flavors) {
if (isPasteFlavorMatch(element, transferable)) {
return true;
}
}
return false;
}
private boolean isPasteFlavorMatch(DataFlavor flavor, Transferable transferable) {
for (ClipboardType type : PASTE_TYPES) {
if (flavor.equals(type.getFlavor())) {
if (type == BYTE_STRING_TYPE) { // our parent handles validating bytes
return isValidBytesTransferable(transferable);
}
return true;
}
}
return false;
}
@Override @Override
public List<ClipboardType> getCurrentCopyTypes() { public List<ClipboardType> getCurrentCopyTypes() {
return COPY_TYPES; return COPY_TYPES;
@ -234,45 +193,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
else if (copyType == COMMENTS_TYPE) { else if (copyType == COMMENTS_TYPE) {
return copyLabelsComments(false, true); return copyLabelsComments(false, true);
} }
else if (copyType == BYTE_STRING_TYPE) {
String byteString = copyBytesAsString(getSelectedAddresses(), true, monitor);
return new ByteViewerTransferable(byteString);
}
else if (copyType == BYTE_STRING_NO_SPACE_TYPE) {
String byteString = copyBytesAsString(getSelectedAddresses(), false, monitor);
return new ByteViewerTransferable(byteString);
}
else if (copyType == PYTHON_BYTE_STRING_TYPE) {
String byteString = "b'"
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", "\\x") + "'";
return new ByteViewerTransferable(byteString);
}
else if (copyType == PYTHON_LIST_TYPE) {
String byteString = "[ "
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " ]";
return new ByteViewerTransferable(byteString);
}
else if (copyType == CPP_BYTE_ARRAY_TYPE) {
String byteString = "{ "
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " }";
return new ByteViewerTransferable(byteString);
}
return null; return copyBytes(copyType, monitor);
}
private AddressSetView getSelectedAddresses() {
AddressSetView addressSet = currentSelection;
if (addressSet == null || addressSet.isEmpty()) {
return new AddressSet(currentLocation.getAddress());
}
return currentSelection;
}
public void setSelection(ProgramSelection selection) {
currentSelection = selection;
copyFromSelectionEnabled = selection != null && !selection.isEmpty();
notifyStateChanged();
} }
public void setStringContent(String text) { public void setStringContent(String text) {
@ -287,6 +209,12 @@ public class CodeBrowserClipboardProvider extends ByteCopier
currentLocation = location; currentLocation = location;
} }
public void setSelection(ProgramSelection selection) {
currentSelection = selection;
copyFromSelectionEnabled = selection != null && !selection.isEmpty();
notifyStateChanged();
}
public void setProgram(Program p) { public void setProgram(Program p) {
currentProgram = p; currentProgram = p;
currentLocation = null; currentLocation = null;
@ -323,11 +251,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
} }
else if (currentLocation instanceof BytesFieldLocation) { else if (currentLocation instanceof BytesFieldLocation) {
// bytes are special--let them get copied and pasted as normal // bytes are special--let them get copied and pasted as normal
// AddressSet addressSet = new AddressSet(currentProgram.getAddressFactory(), address);
// String byteString =
// copyBytesAsString(addressSet.getAddressRanges(), false,
// TaskMonitorAdapter.DUMMY_MONITOR);
// return new ByteViewerTransferable(byteString);
return copyByteString(address); return copyByteString(address);
} }
else if (currentLocation instanceof OperandFieldLocation) { else if (currentLocation instanceof OperandFieldLocation) {
@ -439,7 +362,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier
private Transferable copyByteString(Address address) { private Transferable copyByteString(Address address) {
AddressSet set = new AddressSet(address); AddressSet set = new AddressSet(address);
return copyBytes(set, false, TaskMonitor.DUMMY); return createStringTransferable(copyBytesAsString(set, false, TaskMonitor.DUMMY));
} }
private CodeUnitInfoTransferable copyLabelsComments(boolean copyLabels, boolean copyComments) { private CodeUnitInfoTransferable copyLabelsComments(boolean copyLabels, boolean copyComments) {
@ -451,13 +374,13 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return new CodeUnitInfoTransferable(list); return new CodeUnitInfoTransferable(list);
} }
@SuppressWarnings("unchecked") // assumed correct data; handled in exception case
private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels, private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels,
boolean pasteComments) { boolean pasteComments) {
try { try {
List<CodeUnitInfo> list = (List<CodeUnitInfo>) pasteData.getTransferData( List<?> list = (List<?>) pasteData.getTransferData(
CodeUnitInfoTransferable.localDataTypeFlavor); CodeUnitInfoTransferable.localDataTypeFlavor);
Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), list, pasteLabels, List<CodeUnitInfo> infos = CollectionUtils.asList(list, CodeUnitInfo.class);
Command cmd = new CodeUnitInfoPasteCmd(currentLocation.getAddress(), infos, pasteLabels,
pasteComments); pasteComments);
return tool.execute(cmd, currentProgram); return tool.execute(cmd, currentProgram);
} }
@ -494,36 +417,43 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return tool.execute(cmd, currentProgram); return tool.execute(cmd, currentProgram);
} }
else if (currentLocation instanceof OperandFieldLocation) { else if (currentLocation instanceof OperandFieldLocation) {
OperandFieldLocation operandLocation = (OperandFieldLocation) currentLocation; return pasteOperandField((OperandFieldLocation) currentLocation, labelName);
int opIndex = operandLocation.getOperandIndex(); }
Listing listing = currentProgram.getListing();
Instruction instruction = listing.getInstructionAt(operandLocation.getAddress());
if (instruction == null) {
return false;
}
Reference reference = instruction.getPrimaryReference(opIndex); // try pasting onto something that is not a label
if (reference == null) { return maybePasteNonLabelString(labelName);
return false; }
}
Variable var = currentProgram.getReferenceManager().getReferencedVariable(reference); private boolean pasteOperandField(OperandFieldLocation operandLocation, String labelName) {
if (var != null) {
SetVariableNameCmd cmd =
new SetVariableNameCmd(var, labelName, SourceType.USER_DEFINED);
return tool.execute(cmd, currentProgram);
}
SymbolTable symbolTable = currentProgram.getSymbolTable(); int opIndex = operandLocation.getOperandIndex();
Symbol symbol = symbolTable.getSymbol(reference); Listing listing = currentProgram.getListing();
if ((symbol instanceof CodeSymbol) || (symbol instanceof FunctionSymbol)) { Instruction instruction = listing.getInstructionAt(operandLocation.getAddress());
String oldName = symbol.getName(); if (instruction == null) {
Namespace namespace = symbol.getParentNamespace(); return false;
Address symbolAddress = symbol.getAddress(); }
RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName,
namespace, SourceType.USER_DEFINED); Reference reference = instruction.getPrimaryReference(opIndex);
return tool.execute(cmd, currentProgram); if (reference == null) {
} return false;
}
Variable var = currentProgram.getReferenceManager().getReferencedVariable(reference);
if (var != null) {
SetVariableNameCmd cmd =
new SetVariableNameCmd(var, labelName, SourceType.USER_DEFINED);
return tool.execute(cmd, currentProgram);
}
SymbolTable symbolTable = currentProgram.getSymbolTable();
Symbol symbol = symbolTable.getSymbol(reference);
if ((symbol instanceof CodeSymbol) || (symbol instanceof FunctionSymbol)) {
String oldName = symbol.getName();
Namespace namespace = symbol.getParentNamespace();
Address symbolAddress = symbol.getAddress();
RenameLabelCmd cmd = new RenameLabelCmd(symbolAddress, oldName, labelName,
namespace, SourceType.USER_DEFINED);
return tool.execute(cmd, currentProgram);
} }
// try pasting onto something that is not a label // try pasting onto something that is not a label
@ -707,8 +637,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return true; return true;
} }
if (flavor.equals(DataFlavor.stringFlavor)) { if (flavor.equals(DataFlavor.stringFlavor)) {
// TODO: check if it is a valid hex string... return true; // check if it is a valid hex string?
return true;
} }
} }
} }

View file

@ -15,9 +15,6 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import ghidra.app.util.ClipboardType;
import ghidra.util.task.TaskMonitor;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
import java.util.List; import java.util.List;
@ -26,21 +23,26 @@ import javax.swing.event.ChangeListener;
import docking.ActionContext; import docking.ActionContext;
import docking.ComponentProvider; import docking.ComponentProvider;
import ghidra.app.util.ClipboardType;
import ghidra.util.task.TaskMonitor;
/** /**
* <code>ClipboardContentProvider</code> determines what types of * Determines what types of transfer data can be placed on the clipboard, as well as if
* transfer data can be placed on the clipboard, and cut, copy, and paste. * cut, copy, and paste operations are supported
*/ */
public interface ClipboardContentProviderService { public interface ClipboardContentProviderService {
/** /**
* Returns the component provider associated with this * Returns the component provider associated with this service
* ClipboardContentProviderService. * @return the provider
*/ */
public ComponentProvider getComponentProvider(); public ComponentProvider getComponentProvider();
/** /**
* Triggers the default copy operation * Triggers the default copy operation
* @param monitor monitor that shows progress of the copy to clipboard, and
* may be canceled
* @return the created transferable; null if the copy was unsuccessful
*/ */
public Transferable copy(TaskMonitor monitor); public Transferable copy(TaskMonitor monitor);
@ -49,44 +51,49 @@ public interface ClipboardContentProviderService {
* @param copyType contains the data flavor of the clipboard contents * @param copyType contains the data flavor of the clipboard contents
* @param monitor monitor that shows progress of the copy to clipboard, and * @param monitor monitor that shows progress of the copy to clipboard, and
* may be canceled * may be canceled
* @return the created transferable; null if the copy was unsuccessful
*/ */
public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor); public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor);
/** /**
* Triggers the default paste operation for the given transferable * Triggers the default paste operation for the given transferable
* @param pasteData the paste transferable
* @return true of the paste was successful
*/ */
public boolean paste(Transferable pasteData); public boolean paste(Transferable pasteData);
/** /**
* Gets the currently active ClipboardTypes for copying with the current context * Gets the currently active ClipboardTypes for copying with the current context
* @return the types
*/ */
public List<ClipboardType> getCurrentCopyTypes(); public List<ClipboardType> getCurrentCopyTypes();
/** /**
* Return whether the given context is valid for actions on popup menus. * Return whether the given context is valid for actions on popup menus.
* @param context the context of where the popup menu will be positioned. * @param context the context of where the popup menu will be positioned.
* @return true if valid
*/ */
public boolean isValidContext(ActionContext context); public boolean isValidContext(ActionContext context);
// TODO needs updating. Assumed copyAddToPopup became copy(TaskMonitor monitor)
/** /**
* Returns true if copy should be enabled; false if it should be disabled. This method can * Returns true if copy should be enabled; false if it should be disabled. This method can
* be used in conjunction with {@link #copy(TaskMonitor)} in order to add menu items to * be used in conjunction with {@link #copy(TaskMonitor)} in order to add menu items to
* popup menus but to have them enabled when appropriate. * popup menus but to have them enabled when appropriate.
* @return true if copy should be enabled
*/ */
public boolean enableCopy(); public boolean enableCopy();
/** /**
* Returns true if copySpecial actions should be enabled; * Returns true if copySpecial actions should be enabled;
* @return * @return true if copySpecial actions should be enabled;
*/ */
public boolean enableCopySpecial(); public boolean enableCopySpecial();
// TODO needs updating. Assumed pasteAddToPopup became paste(Transferable pasteData)
/** /**
* Returns true if paste should be enabled; false if it should be disabled. This method can * Returns true if paste should be enabled; false if it should be disabled. This method can
* be used in conjunction with {@link #paste(Transferable)} in order to add menu items to * be used in conjunction with {@link #paste(Transferable)} in order to add menu items to
* popup menus but to have them enabled when appropriate. * popup menus but to have them enabled when appropriate.
* @return true if paste should be enabled
*/ */
public boolean enablePaste(); public boolean enablePaste();
@ -130,6 +137,7 @@ public interface ClipboardContentProviderService {
/** /**
* Returns true if the given service provider can currently perform a 'copy special' * Returns true if the given service provider can currently perform a 'copy special'
* operation. * operation.
* @return true if copy special is enabled
*/ */
public boolean canCopySpecial(); public boolean canCopySpecial();
} }

View file

@ -18,6 +18,8 @@ package ghidra.app.util;
import java.awt.datatransfer.*; import java.awt.datatransfer.*;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import docking.dnd.GenericDataFlavor; import docking.dnd.GenericDataFlavor;
import docking.dnd.StringTransferable; import docking.dnd.StringTransferable;
@ -25,8 +27,7 @@ import docking.widgets.OptionDialog;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
@ -64,6 +65,19 @@ public abstract class ByteCopier {
public static final ClipboardType CPP_BYTE_ARRAY_TYPE = public static final ClipboardType CPP_BYTE_ARRAY_TYPE =
new ClipboardType(CPP_BYTE_ARRAY_FLAVOR, "C Array"); new ClipboardType(CPP_BYTE_ARRAY_FLAVOR, "C Array");
private static final Map<DataFlavor, Pattern> PROGRAMMING_PATTERNS_BY_FLAVOR =
Map.of(
PYTHON_BYTE_STRING_FLAVOR, Pattern.compile("b'(.*)'"),
PYTHON_LIST_FLAVOR, Pattern.compile("\\[(.*)\\]"),
CPP_BYTE_ARRAY_FLAVOR, Pattern.compile("\\{(.*)\\}"));
/**
* Pattern to recognize bytes that have been encoded during a copy operation using one of this
* class's programming copy types
*/
private static final Pattern PROGRAMMING_BYTES_PATTERN =
Pattern.compile("(?:\\\\x|0x)([a-fA-F0-9]{2})");
private static DataFlavor createByteStringLocalDataTypeFlavor() { private static DataFlavor createByteStringLocalDataTypeFlavor() {
try { try {
@ -72,7 +86,7 @@ public abstract class ByteCopier {
"Local flavor--byte string with spaces"); "Local flavor--byte string with spaces");
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", Msg.error(ByteCopier.class,
"Unexpected exception creating data flavor for byte string", e); "Unexpected exception creating data flavor for byte string", e);
} }
@ -87,7 +101,7 @@ public abstract class ByteCopier {
"Local flavor--byte string with NO spaces"); "Local flavor--byte string with NO spaces");
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", Msg.error(ByteCopier.class,
"Unexpected exception creating data flavor for byte string with no spaces", e); "Unexpected exception creating data flavor for byte string with no spaces", e);
} }
@ -102,7 +116,7 @@ public abstract class ByteCopier {
"Local flavor--Python byte string"); "Local flavor--Python byte string");
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", Msg.error(ByteCopier.class,
"Unexpected exception creating data flavor for Python byte string", e); "Unexpected exception creating data flavor for Python byte string", e);
} }
@ -117,7 +131,7 @@ public abstract class ByteCopier {
"Local flavor--Python list"); "Local flavor--Python list");
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", Msg.error(ByteCopier.class,
"Unexpected exception creating data flavor for Python list", e); "Unexpected exception creating data flavor for Python list", e);
} }
@ -132,7 +146,7 @@ public abstract class ByteCopier {
"Local flavor--C++ array"); "Local flavor--C++ array");
} }
catch (Exception e) { catch (Exception e) {
Msg.showError(ByteCopier.class, null, "Could Not Create Data Flavor", Msg.error(ByteCopier.class,
"Unexpected exception creating data flavor for C array", e); "Unexpected exception creating data flavor for C array", e);
} }
@ -148,8 +162,12 @@ public abstract class ByteCopier {
// limit construction // limit construction
} }
protected Transferable copyBytes(boolean includeSpaces, TaskMonitor monitor) { protected AddressSetView getSelectedAddresses() {
return copyBytes(currentSelection, includeSpaces, monitor); AddressSetView addressSet = currentSelection;
if (addressSet == null || addressSet.isEmpty()) {
return new AddressSet(currentLocation.getAddress());
}
return currentSelection;
} }
protected Transferable copyBytes(AddressSetView addresses, boolean includeSpaces, protected Transferable copyBytes(AddressSetView addresses, boolean includeSpaces,
@ -160,42 +178,18 @@ public abstract class ByteCopier {
protected String copyBytesAsString(AddressSetView addresses, boolean includeSpaces, protected String copyBytesAsString(AddressSetView addresses, boolean includeSpaces,
TaskMonitor monitor) { TaskMonitor monitor) {
Memory memory = currentProgram.getMemory();
String delimiter = includeSpaces ? " " : ""; String delimiter = includeSpaces ? " " : "";
return copyBytesAsString(addresses, delimiter, monitor);
}
protected String copyBytesAsString(AddressSetView addresses, String delimiter,
TaskMonitor monitor) {
Memory memory = currentProgram.getMemory();
ByteIterator bytes = new ByteIterator(addresses, memory); ByteIterator bytes = new ByteIterator(addresses, memory);
return NumericUtilities.convertBytesToString(bytes, delimiter); return NumericUtilities.convertBytesToString(bytes, delimiter);
} }
protected boolean supportsPasteTransferable(Transferable transferable) {
return isValidBytesTransferable(transferable);
}
protected boolean isValidBytesTransferable(Transferable transferable) {
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for (DataFlavor element : flavors) {
try {
Object object = transferable.getTransferData(element);
if (object instanceof String) {
String string = (String) object;
if (!isOnlyAsciiBytes(string)) {
tool.setStatusInfo("Paste string contains non-text ascii bytes. " +
"Only the ascii text will be pasted.", true);
string = keepOnlyAsciiBytes(string);
}
return (getBytes(string) != null);
}
}
catch (Exception e) {
// don't care; try the next one
}
}
return false;
}
private byte[] getBytes(String transferString) { private byte[] getBytes(String transferString) {
byte[] bytes = getHexBytes(transferString); byte[] bytes = getHexBytes(transferString);
@ -271,27 +265,121 @@ public abstract class ByteCopier {
return null; return null;
} }
protected Transferable copyBytes(ClipboardType copyType, TaskMonitor monitor) {
if (copyType == BYTE_STRING_TYPE) {
String byteString = copyBytesAsString(getSelectedAddresses(), true, monitor);
return new ByteStringTransferable(byteString);
}
else if (copyType == BYTE_STRING_NO_SPACE_TYPE) {
String byteString = copyBytesAsString(getSelectedAddresses(), false, monitor);
return new ByteStringTransferable(byteString);
}
else if (copyType == PYTHON_BYTE_STRING_TYPE) {
String prefix = "\\x";
String bs = copyBytesAsString(getSelectedAddresses(), prefix, monitor);
String fixed = "b'" + prefix + bs + "'";
return new ProgrammingByteStringTransferable(fixed, copyType.getFlavor());
}
else if (copyType == PYTHON_LIST_TYPE) {
String prefix = "0x";
String bs = copyBytesAsString(getSelectedAddresses(), ", " + prefix, monitor);
String fixed = "[ " + prefix + bs + " ]";
return new ProgrammingByteStringTransferable(fixed, copyType.getFlavor());
}
else if (copyType == CPP_BYTE_ARRAY_TYPE) {
String prefix = "0x";
String bs = copyBytesAsString(getSelectedAddresses(), ", " + prefix, monitor);
String byteString = "{ " + prefix + bs + " }";
return new ProgrammingByteStringTransferable(byteString, copyType.getFlavor());
}
return null;
}
protected boolean pasteBytes(Transferable pasteData) protected boolean pasteBytes(Transferable pasteData)
throws UnsupportedFlavorException, IOException { throws UnsupportedFlavorException, IOException {
if (!supportsPasteTransferable(pasteData)) {
tool.setStatusInfo("Paste failed: No valid data on clipboard", true); DataFlavor[] flavors = pasteData.getTransferDataFlavors();
DataFlavor byteStringFlavor = getByteStringFlavor(flavors);
if (byteStringFlavor != null) {
String data = (String) pasteData.getTransferData(byteStringFlavor);
return pasteByteString(data);
}
DataFlavor programmingFlavor = getProgrammingFlavor(flavors);
if (programmingFlavor != null) {
String data = (String) pasteData.getTransferData(programmingFlavor);
String byteString = extractProgrammingBytes(programmingFlavor, data);
if (byteString != null) {
return pasteByteString(byteString);
}
}
if (!pasteData.isDataFlavorSupported(DataFlavor.stringFlavor)) {
tool.setStatusInfo("Paste failed: unsupported data type", true);
return false; return false;
} }
if (pasteData.isDataFlavorSupported(BYTE_STRING_FLAVOR)) { // see if the pasted data is similar to other known programming formats
String data = (String) pasteData.getTransferData(BYTE_STRING_FLAVOR);
return pasteByteString(data);
}
if (pasteData.isDataFlavorSupported(BYTE_STRING_NO_SPACES_FLAVOR)) {
String data = (String) pasteData.getTransferData(BYTE_STRING_NO_SPACES_FLAVOR);
return pasteByteString(data);
}
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor); String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor);
if (string == null) {
tool.setStatusInfo("Paste failed: no string data", true);
return false;
}
return pasteByteString(string); return pasteByteString(string);
} }
private DataFlavor getProgrammingFlavor(DataFlavor[] flavors) {
for (DataFlavor flavor : flavors) {
if (flavor.equals(PYTHON_BYTE_STRING_FLAVOR) ||
flavor.equals(PYTHON_LIST_FLAVOR) ||
flavor.equals(CPP_BYTE_ARRAY_FLAVOR)) {
return flavor;
}
}
return null;
}
private DataFlavor getByteStringFlavor(DataFlavor[] flavors) {
for (DataFlavor flavor : flavors) {
if (flavor.equals(BYTE_STRING_FLAVOR) ||
flavor.equals(BYTE_STRING_NO_SPACES_FLAVOR)) {
return flavor;
}
}
return null;
}
private String extractProgrammingBytes(DataFlavor flavor, String data) {
Pattern pattern = PROGRAMMING_PATTERNS_BY_FLAVOR.get(flavor);
Matcher matcher = pattern.matcher(data);
if (!matcher.matches()) {
return null;
}
String bytes = matcher.group(1);
if (bytes == null) {
return null;
}
Matcher bytesMatcher = PROGRAMMING_BYTES_PATTERN.matcher(bytes);
if (!bytesMatcher.find()) {
return null;
}
StringBuilder buffy = new StringBuilder();
buffy.append(bytesMatcher.group(1));
while (bytesMatcher.find()) {
buffy.append(bytesMatcher.group(1));
}
return buffy.toString();
}
protected boolean pasteByteString(final String string) { protected boolean pasteByteString(final String string) {
Command cmd = new Command() { Command cmd = new Command() {
@ -299,75 +387,100 @@ public abstract class ByteCopier {
@Override @Override
public boolean applyTo(DomainObject domainObject) { public boolean applyTo(DomainObject domainObject) {
if (domainObject instanceof Program) { if (!(domainObject instanceof Program)) {
String validString = string; return false;
if (!isOnlyAsciiBytes(string)) { }
tool.setStatusInfo("Pasted string contained non-text ascii bytes. " +
"Only the ascii text was pasted.", true);
validString = keepOnlyAsciiBytes(string); String validString = string;
if (!isOnlyAsciiBytes(string)) {
tool.setStatusInfo("Pasted string contained non-text ascii bytes. " +
"Only the ascii will be used.", true);
validString = keepOnlyAsciiBytes(string);
}
byte[] bytes = getBytes(validString);
if (bytes == null) {
status = "Improper data format. Expected sequence of hex bytes";
tool.beep();
return false;
}
// Ensure that we are not writing over instructions
Program program = (Program) domainObject;
Address address = currentLocation.getAddress();
if (!hasEnoughSpace(program, address, bytes.length)) {
status =
"Not enough space to paste all bytes. Encountered data or instructions.";
tool.beep();
return false;
}
// Ask the user before pasting a string into the program. Since having a string in
// the clipboard is so common, this is to prevent an accidental paste.
if (!confirmPaste(validString)) {
return true; // the user cancelled; the command is successful
}
boolean pastedAllBytes = pasteBytes(program, bytes);
if (!pastedAllBytes) {
tool.setStatusInfo("Not all bytes were pasted due to memory access issues",
true);
}
return true;
}
private boolean pasteBytes(Program program, byte[] bytes) {
// note: loop one byte at a time here, since Memory will validate all addresses
// before pasting any bytes
boolean foundError = false;
Address address = currentLocation.getAddress();
Memory memory = program.getMemory();
for (byte element : bytes) {
try {
memory.setByte(address, element);
} }
catch (MemoryAccessException e) {
// Keep trying the remaining bytes. Should we just stop in this case?
foundError = true;
}
address = address.next();
}
return foundError;
}
byte[] bytes = getBytes(validString); private boolean confirmPaste(String validString) {
if (bytes == null) {
status = "Improper data format (expected sequence of hex bytes)"; // create a truncated version of the string to show in the dialog
String partialText = validString.length() < 40 ? validString
: validString.substring(0, 40) + " ...";
int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?",
"Are you sure you want to paste the string \"" + partialText +
"\"\n into the program's memory?");
return result != OptionDialog.NO_OPTION;
}
private boolean hasEnoughSpace(Program program, Address address, int byteCount) {
Listing listing = program.getListing();
for (int i = 0; i < byteCount;) {
if (address == null) {
status = "Not enough addresses to paste bytes";
tool.beep(); tool.beep();
return false; return false;
} }
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
// Ensure that we are not writing over instructions if (!(codeUnit instanceof Data) || ((Data) codeUnit).isDefined()) {
Program curProgram = (Program) domainObject; status = "Cannot paste on top of defined instructions/data";
Listing listing = curProgram.getListing(); tool.beep();
Address curAddr = currentLocation.getAddress(); return false;
int byteCount = bytes.length;
for (int i = 0; i < byteCount;) {
if (curAddr == null) {
status = "Not enough addresses to paste bytes";
tool.beep();
return false;
}
CodeUnit curCodeUnit = listing.getCodeUnitContaining(curAddr);
if (!(curCodeUnit instanceof Data) || ((Data) curCodeUnit).isDefined()) {
status = "Cannot paste on top of defined instructions/data";
tool.beep();
return false;
}
int length = curCodeUnit.getLength();
i += length;
curAddr = curCodeUnit.getMaxAddress().next();
} }
int length = codeUnit.getLength();
// Per SCR 11212, ask the user before pasting a string into the program. i += length;
// Since having a string in the clipboard is so common, this is to prevent address = codeUnit.getMaxAddress().next();
// an accidental paste.
// create a truncated version of the string to show in the dialog
String partialText = validString.length() < 40 ? validString
: validString.substring(0, 40) + " ...";
int result = OptionDialog.showYesNoDialog(null, "Paste String Into Program?",
"Are you sure you want to paste the string \"" + partialText +
"\"\n into the program's memory?");
if (result == OptionDialog.NO_OPTION) {
return true;
}
// Write data
curAddr = currentLocation.getAddress();
for (byte element : bytes) {
try {
curProgram.getMemory().setByte(curAddr, element);
}
catch (MemoryAccessException e1) {
// handle below
}
curAddr = curAddr.next();
}
return true;
} }
return false; return true;
} }
@Override @Override
@ -445,33 +558,69 @@ public abstract class ByteCopier {
} }
} }
public static class ByteViewerTransferable implements Transferable { public static class ProgrammingByteStringTransferable implements Transferable {
private final DataFlavor[] flavors = { BYTE_STRING_NO_SPACE_TYPE.getFlavor(), private List<DataFlavor> flavorList;
BYTE_STRING_TYPE.getFlavor(), PYTHON_BYTE_STRING_TYPE.getFlavor(), private DataFlavor[] flavors;
PYTHON_LIST_TYPE.getFlavor(), CPP_BYTE_ARRAY_TYPE.getFlavor(), private DataFlavor programmingFlavor;
DataFlavor.stringFlavor }; private String byteString;
private final List<DataFlavor> flavorList = Arrays.asList(flavors);
private final String byteString; public ProgrammingByteStringTransferable(String byteString, DataFlavor flavor) {
private final String byteViewerRepresentation;
public ByteViewerTransferable(String byteString) {
this(byteString, null);
}
public ByteViewerTransferable(String byteString, String byteViewerRepresentation) {
this.byteString = byteString; this.byteString = byteString;
this.byteViewerRepresentation = byteViewerRepresentation; this.programmingFlavor = flavor;
this.flavors = new DataFlavor[] { flavor, DataFlavor.stringFlavor };
this.flavorList = Arrays.asList(flavors);
} }
@Override @Override
public Object getTransferData(DataFlavor flavor) public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException { throws UnsupportedFlavorException, IOException {
if (flavor.equals(DataFlavor.stringFlavor)) { if (flavor.equals(DataFlavor.stringFlavor)) {
if (byteViewerRepresentation != null) { return byteString; // just default to the byte string when no 'special' string data
return byteViewerRepresentation; }
if (flavor.equals(programmingFlavor)) {
return byteString;
}
throw new UnsupportedFlavorException(flavor);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavorList.contains(flavor);
}
}
public static class ByteStringTransferable implements Transferable {
private final DataFlavor[] flavors = {
BYTE_STRING_NO_SPACE_TYPE.getFlavor(),
BYTE_STRING_TYPE.getFlavor(),
DataFlavor.stringFlavor };
private final List<DataFlavor> flavorList = Arrays.asList(flavors);
private final String byteString;
private final String stringRepresentation;
public ByteStringTransferable(String byteString) {
this(byteString, null);
}
public ByteStringTransferable(String byteString, String stringRepresentation) {
this.byteString = byteString;
this.stringRepresentation = stringRepresentation;
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (flavor.equals(DataFlavor.stringFlavor)) {
if (stringRepresentation != null) {
return stringRepresentation;
} }
return byteString; // just default to the byte string when no 'special' string data return byteString; // just default to the byte string when no 'special' string data
} }
@ -481,15 +630,6 @@ public abstract class ByteCopier {
if (flavor.equals(BYTE_STRING_NO_SPACE_TYPE.getFlavor())) { if (flavor.equals(BYTE_STRING_NO_SPACE_TYPE.getFlavor())) {
return byteString; return byteString;
} }
if (flavor.equals(PYTHON_BYTE_STRING_TYPE.getFlavor())) {
return byteString;
}
if (flavor.equals(PYTHON_LIST_TYPE.getFlavor())) {
return byteString;
}
if (flavor.equals(CPP_BYTE_ARRAY_TYPE.getFlavor())) {
return byteString;
}
throw new UnsupportedFlavorException(flavor); throw new UnsupportedFlavorException(flavor);
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,7 +21,7 @@ import java.awt.datatransfer.DataFlavor;
* Defines a "type" for items in the Clipboard * Defines a "type" for items in the Clipboard
*/ */
public class ClipboardType { public class ClipboardType {
private DataFlavor flavor; private DataFlavor flavor;
private String typeName; private String typeName;
@ -35,26 +34,25 @@ public class ClipboardType {
this.flavor = flavor; this.flavor = flavor;
this.typeName = typeName; this.typeName = typeName;
} }
/** /**
* Returns the DataFlavor for this ClipboardType * Returns the DataFlavor for this type
* @return the flavor
*/ */
public DataFlavor getFlavor() { public DataFlavor getFlavor() {
return flavor; return flavor;
} }
/** /**
* Returns the name of this Clipboard Type. * Returns the name of this type
* @return the name
*/ */
public String getTypeName() { public String getTypeName() {
return typeName; return typeName;
} }
/**
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
return typeName; return typeName;
} }
} }

View file

@ -0,0 +1,224 @@
/* ###
* 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.clipboard;
import static org.hamcrest.core.IsInstanceOf.*;
import static org.junit.Assert.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import org.junit.Before;
import org.junit.Test;
import docking.dnd.StringTransferable;
import docking.widgets.OptionDialog;
import ghidra.app.util.ByteCopier;
import ghidra.app.util.ByteCopier.ProgrammingByteStringTransferable;
import ghidra.app.util.ClipboardType;
import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.DummyTool;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.AssertException;
import ghidra.util.task.TaskMonitor;
public class CodeBrowserClipboardProviderTest extends AbstractGhidraHeadedIntegrationTest {
private Program program;
private CodeBrowserClipboardProvider clipboardProvider;
@Before
public void setUp() throws Exception {
program = createProgram();
PluginTool tool = new DummyTool() {
@Override
public boolean execute(Command command, DomainObject obj) {
boolean result = command.applyTo(obj);
if (!result) {
throw new AssertException("Failed to write bytes");
}
return true;
}
};
clipboardProvider = new CodeBrowserClipboardProvider(tool, null);
clipboardProvider.setProgram(program);
}
private Program createProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("default", ProgramBuilder._TOY, this);
builder.createMemory("test", "0x01001050", 20000);
builder.setBytes("0x01001050",
"0e 5e f4 77 33 58 f4 77 91 45 f4 77 88 7c f4 77 8d 70 f5 77 05 62 f4 77 f0 a3 " +
"f4 77 09 56 f4 77 10 17 f4 77 f7 29 f6 77 02 59 f4 77");
builder.setBytes("0x01002050", "00 00 00 00 00 00 00 00 00 00 00 00 00");
builder.createMemoryReference("0x01002cc0", "0x01002cf0", RefType.DATA,
SourceType.USER_DEFINED);
builder.createMemoryReference("0x01002d04", "0x01002d0f", RefType.DATA,
SourceType.USER_DEFINED);
DataType dt = DataType.DEFAULT;
Parameter p = new ParameterImpl(null, dt, builder.getProgram());
builder.createEmptyFunction("ghidra", "0x01002cf5", 1, dt, p);
builder.createEmptyFunction("sscanf", "0x0100415a", 1, dt, p);
builder.setBytes("0x0100418c", "ff 15 08 10 00 01");
builder.disassemble("0x0100418c", 6);
return builder.getProgram();
}
@Test
public void testCopyPasteSpecial_PythonByteString() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", length));
ClipboardType type = ByteCopier.PYTHON_BYTE_STRING_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("b'\\x0e\\x5e\\xf4\\x77'", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPasteSpecial_PythonListString() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", 4));
ClipboardType type = ByteCopier.PYTHON_LIST_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("[ 0x0e, 0x5e, 0xf4, 0x77 ]", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPasteSpecial_CppByteArray() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", 4));
ClipboardType type = ByteCopier.CPP_BYTE_ARRAY_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("{ 0x0e, 0x5e, 0xf4, 0x77 }", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPaste_ByteString() throws Exception {
String byteString = "0e 5e f4 77";
StringTransferable transferable = new StringTransferable(byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, byteString, 4);
}
@Test
public void testCopyPaste_ByteString_MixedWithNonAscii() throws Exception {
// the byte string contains ascii and non-ascii
String byteString =
"0e " + ((char) 0x80) + " 5e " + ((char) 0x81 + " f4 " + ((char) 0x82)) + " 77";
String asciiByteString = "0e 5e f4 77";
StringTransferable transferable = new StringTransferable(byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, asciiByteString, 4);
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void paste(String address, Transferable transferable) {
tx(program, () -> {
doPaste(address, transferable);
});
}
private void doPaste(String address, Transferable transferable) {
clipboardProvider.setLocation(location(address));
runSwing(() -> clipboardProvider.paste(transferable), false);
OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(confirmDialog, "Yes");
waitForTasks();
program.flushEvents();
waitForSwing();
}
private void assertBytesAt(String address, String bytes, int length)
throws MemoryAccessException {
Memory memory = program.getMemory();
byte[] memoryBytes = new byte[length];
memory.getBytes(addr(address), memoryBytes, 0, length);
String memoryByteString = NumericUtilities.convertBytesToString(memoryBytes, " ");
assertEquals(bytes, memoryByteString);
}
private ProgramSelection selection(String addressString, int n) {
Address address = addr(addressString);
AddressSetView addresses = new AddressSet(address, address.add(n - 1));
return new ProgramSelection(addresses);
}
private Address addr(String addr) {
return program.getAddressFactory().getAddress(addr);
}
private ProgramLocation location(String addressString) {
return new ProgramLocation(program, addr(addressString));
}
}

View file

@ -132,6 +132,7 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
pmTwo.openProgram(df2); pmTwo.openProgram(df2);
programTwo = (ProgramDB) pmTwo.getCurrentProgram(); programTwo = (ProgramDB) pmTwo.getCurrentProgram();
}); });
} }
@Test @Test
@ -145,7 +146,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
goTo(toolOne, 0x32a); goTo(toolOne, 0x32a);
ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class); ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste"); ClipboardContentProviderService service = getClipboardService(plugin);
DockingActionIf pasteAction = getLocalAction(service, "Paste", plugin);
assertEnabled(pasteAction, cb.getProvider()); assertEnabled(pasteAction, cb.getProvider());
} }
@ -194,8 +196,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
// in Program One, add MyLabel at 032a // in Program One, add MyLabel at 032a
int transactionID = programOne.startTransaction("test"); int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x032a), "MyLabel", programOne.getSymbolTable()
SourceType.USER_DEFINED); .createLabel(addr(programOne, 0x032a), "MyLabel",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true); programOne.endTransaction(transactionID, true);
goTo(toolTwo, 0x0326); goTo(toolTwo, 0x0326);
@ -385,8 +388,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
programOne.getSymbolTable().getSymbol("LAB_0331", addr(programOne, 0x0331), null); programOne.getSymbolTable().getSymbol("LAB_0331", addr(programOne, 0x0331), null);
// in Browser(1) change default label at 331 to JUNK // in Browser(1) change default label at 331 to JUNK
int transactionID = programOne.startTransaction("test"); int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x0331), "JUNK", programOne.getSymbolTable()
SourceType.USER_DEFINED); .createLabel(addr(programOne, 0x0331), "JUNK",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true); programOne.endTransaction(transactionID, true);
// //
// in Browser(1) go to 331 // in Browser(1) go to 331
@ -426,8 +430,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
public void testPasteAtMultipleLabels() throws Exception { public void testPasteAtMultipleLabels() throws Exception {
// in program 2, create a second label, JUNK2, at 0331 // in program 2, create a second label, JUNK2, at 0331
int transactionID = programOne.startTransaction("test"); int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2", programOne.getSymbolTable()
SourceType.USER_DEFINED); .createLabel(addr(programOne, 0x331), "JUNK2",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true); programOne.endTransaction(transactionID, true);
// in Browser(2) select 331 through 334, contains "RSR10" // in Browser(2) select 331 through 334, contains "RSR10"
@ -474,8 +479,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
@Test @Test
public void testPasteWhereUserLabelExists() throws Exception { public void testPasteWhereUserLabelExists() throws Exception {
int transactionID = programOne.startTransaction("test"); int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2", programOne.getSymbolTable()
SourceType.USER_DEFINED); .createLabel(addr(programOne, 0x331), "JUNK2",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true); programOne.endTransaction(transactionID, true);
// in Browser(2) select 331 through 334, contains "RSR10" // in Browser(2) select 331 through 334, contains "RSR10"
@ -586,13 +592,19 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
cb.goToField(addr(programOne, 0x0331), LabelFieldFactory.FIELD_NAME, 0, 0); cb.goToField(addr(programOne, 0x0331), LabelFieldFactory.FIELD_NAME, 0, 0);
f = (ListingTextField) cb.getCurrentField(); f = (ListingTextField) cb.getCurrentField();
assertEquals(programOne.getSymbolTable().getSymbol("LAB_00000331", addr(programOne, 0x0331), assertEquals(programOne.getSymbolTable()
null).getName(), f.getText()); .getSymbol("LAB_00000331", addr(programOne, 0x0331),
null)
.getName(),
f.getText());
cb.goToField(addr(programOne, 0x031b), LabelFieldFactory.FIELD_NAME, 0, 0); cb.goToField(addr(programOne, 0x031b), LabelFieldFactory.FIELD_NAME, 0, 0);
f = (ListingTextField) cb.getCurrentField(); f = (ListingTextField) cb.getCurrentField();
assertEquals(programOne.getSymbolTable().getSymbol("LAB_0000031b", addr(programOne, 0x031b), assertEquals(programOne.getSymbolTable()
null).getName(), f.getText()); .getSymbol("LAB_0000031b", addr(programOne, 0x031b),
null)
.getName(),
f.getText());
redo(programOne); redo(programOne);
@ -616,8 +628,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
// create a function over the range 0x31b through 0x0343. // create a function over the range 0x31b through 0x0343.
int transactionID = programOne.startTransaction("test"); int transactionID = programOne.startTransaction("test");
String name = SymbolUtilities.getDefaultFunctionName(min); String name = SymbolUtilities.getDefaultFunctionName(min);
programOne.getListing().createFunction(name, min, new AddressSet(min, max), programOne.getListing()
SourceType.USER_DEFINED); .createFunction(name, min, new AddressSet(min, max),
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true); programOne.endTransaction(transactionID, true);
programOne.flushEvents(); programOne.flushEvents();
waitForSwing(); waitForSwing();
@ -710,19 +723,23 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
private void copyToolTwoLabels() { private void copyToolTwoLabels() {
ClipboardPlugin plugin = getPlugin(toolTwo, ClipboardPlugin.class); ClipboardPlugin plugin = getPlugin(toolTwo, ClipboardPlugin.class);
ClipboardContentProviderService service = ClipboardContentProviderService service =
getCodeBrowserClipboardContentProviderService(plugin); getClipboardService(plugin);
DockingAction action = getLocalAction(service, "Copy Special", plugin); DockingAction action = getLocalAction(service, "Copy Special", plugin);
assertNotNull(action); assertNotNull(action);
assertEnabled(action, cb2.getProvider()); assertEnabled(action, cb2.getProvider());
plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE); runSwing(
() -> plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE));
} }
private void pasteToolOne() { private void pasteToolOne() {
ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class); ClipboardPlugin plugin = getPlugin(toolOne, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste"); ClipboardContentProviderService service = getClipboardService(plugin);
DockingActionIf pasteAction = getLocalAction(service, "Paste", plugin);
assertEnabled(pasteAction, cb.getProvider()); assertEnabled(pasteAction, cb.getProvider());
performAction(pasteAction, true); performAction(pasteAction, true);
waitForSwing();
} }
private void setupTool(PluginTool tool) throws Exception { private void setupTool(PluginTool tool) throws Exception {
@ -809,13 +826,13 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
waitForSwing(); waitForSwing();
} }
private ClipboardContentProviderService getCodeBrowserClipboardContentProviderService( private ClipboardContentProviderService getClipboardService(
ClipboardPlugin clipboardPlugin) { ClipboardPlugin clipboardPlugin) {
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin); Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
Set<?> keySet = serviceMap.keySet(); Set<?> keySet = serviceMap.keySet();
for (Object name : keySet) { for (Object name : keySet) {
ClipboardContentProviderService service = (ClipboardContentProviderService) name; ClipboardContentProviderService service = (ClipboardContentProviderService) name;
if (service instanceof CodeBrowserClipboardProvider) { if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
return service; return service;
} }
} }
@ -825,17 +842,12 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName, private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName,
ClipboardPlugin clipboardPlugin) { ClipboardPlugin clipboardPlugin) {
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin); Map<?, ?> actionsByService =
Set<?> keySet = serviceMap.keySet(); (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
for (Object name : keySet) { List<DockingAction> actionList = (List<DockingAction>) actionsByService.get(service);
ClipboardContentProviderService currentService = (ClipboardContentProviderService) name; for (DockingAction pluginAction : actionList) {
if (currentService == service) { if (pluginAction.getName().equals(actionName)) {
List<DockingAction> actionList = (List<DockingAction>) serviceMap.get(service); return pluginAction;
for (DockingAction pluginAction : actionList) {
if (pluginAction.getName().equals(actionName)) {
return pluginAction;
}
}
} }
} }
@ -844,7 +856,9 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
private void assertEnabled(DockingActionIf action, ComponentProvider provider) { private void assertEnabled(DockingActionIf action, ComponentProvider provider) {
boolean isEnabled = boolean isEnabled =
runSwing(() -> action.isEnabledForContext(provider.getActionContext(null))); runSwing(() -> {
assertTrue(isEnabled); return action.isEnabledForContext(provider.getActionContext(null));
});
assertTrue("Action was not enabled when it should be", isEnabled);
} }
} }

View file

@ -15,8 +15,7 @@
*/ */
package ghidra.app.plugin.core.clipboard; package ghidra.app.plugin.core.clipboard;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;
import java.awt.Point; import java.awt.Point;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@ -26,6 +25,7 @@ import javax.swing.SwingUtilities;
import org.junit.*; import org.junit.*;
import docking.action.DockingAction;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import ghidra.app.cmd.function.SetVariableCommentCmd; import ghidra.app.cmd.function.SetVariableCommentCmd;
@ -53,11 +53,8 @@ import ghidra.program.util.ProgramSelection;
import ghidra.test.*; import ghidra.test.*;
/** /**
* Test copy/paste function information. * Test copy/paste function information
*
*
*/ */
public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTest { public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env; private TestEnv env;
@ -72,14 +69,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
private Options fieldOptions2; private Options fieldOptions2;
private CodeBrowserPlugin cb1; private CodeBrowserPlugin cb1;
/**
* Constructor for CopyPasteFunctionInfoTest.
* @param arg0
*/
public CopyPasteFunctionInfoTest() {
super();
}
private Program buildNotepad(String name) throws Exception { private Program buildNotepad(String name) throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder(name, true, ProgramBuilder._TOY); ToyProgramBuilder builder = new ToyProgramBuilder(name, true, ProgramBuilder._TOY);
builder.createMemory("test1", "0x01001000", 0x8000); builder.createMemory("test1", "0x01001000", 0x8000);
@ -133,9 +122,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
resetOptions(); resetOptions();
} }
/*
* @see TestCase#tearDown()
*/
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
env.dispose(); env.dispose();
@ -174,12 +160,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, 0x1004700); goToAddr(toolTwo, 0x1004700);
click2(); click2();
// paste paste(toolTwo);
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
// function FUN_01004700 should be renamed to "ghidra" // function FUN_01004700 should be renamed to "ghidra"
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class); CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
@ -219,12 +200,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, entryAddr); goToAddr(toolTwo, entryAddr);
click2(); click2();
// paste paste(toolTwo);
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class); CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
cb.goToField(entryAddr, PlateFieldFactory.FIELD_NAME, 0, 0); cb.goToField(entryAddr, PlateFieldFactory.FIELD_NAME, 0, 0);
@ -283,12 +259,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, addr); goToAddr(toolTwo, addr);
click2(); click2();
// paste paste(toolTwo);
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
// verify the code browser field shows the comment // verify the code browser field shows the comment
func = programTwo.getListing().getFunctionAt(addr); func = programTwo.getListing().getFunctionAt(addr);
@ -338,12 +309,8 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
addr = getAddr(programTwo, 0x01004260); addr = getAddr(programTwo, 0x01004260);
goToAddr(toolTwo, addr); goToAddr(toolTwo, addr);
click2(); click2();
// paste
plugin = getPlugin(toolTwo, ClipboardPlugin.class); paste(toolTwo);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
// verify the code browser field shows the comment // verify the code browser field shows the comment
func = programTwo.getListing().getFunctionAt(addr); func = programTwo.getListing().getFunctionAt(addr);
@ -416,12 +383,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, 0x0100176f); goToAddr(toolTwo, 0x0100176f);
click2(); click2();
// paste paste(toolTwo);
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
addr = getAddr(programTwo, 0x0100176f); addr = getAddr(programTwo, 0x0100176f);
// nothing should happen with the stack variable comments // nothing should happen with the stack variable comments
@ -433,7 +395,36 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
assertEquals(1, f.getNumRows()); assertEquals(1, f.getNumRows());
} }
///////////////////////////////////////////////////////////////////////// //==================================================================================================
// Private Methods
//==================================================================================================
private void paste(PluginTool tool) {
ClipboardPlugin plugin = getPlugin(tool, ClipboardPlugin.class);
ClipboardContentProviderService service =
getCodeBrowserClipboardContentProviderService(plugin);
DockingActionIf pasteAction = getClipboardAction(plugin, service, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
}
private DockingActionIf getClipboardAction(ClipboardPlugin plugin,
ClipboardContentProviderService service, String actionName) {
@SuppressWarnings("unchecked")
Map<ClipboardContentProviderService, List<DockingAction>> map =
(Map<ClipboardContentProviderService, List<DockingAction>>) getInstanceField(
"serviceActionMap", plugin);
List<DockingAction> list = map.get(service);
for (DockingAction pluginAction : list) {
if (pluginAction.getName().equals(actionName)) {
return pluginAction;
}
}
return null;
}
private void setupTool(PluginTool tool) throws Exception { private void setupTool(PluginTool tool) throws Exception {
tool.addPlugin(ClipboardPlugin.class.getName()); tool.addPlugin(ClipboardPlugin.class.getName());
@ -511,10 +502,9 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
ClipboardPlugin clipboardPlugin) { ClipboardPlugin clipboardPlugin) {
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin); Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
Set<?> keySet = serviceMap.keySet(); Set<?> keySet = serviceMap.keySet();
for (Object name : keySet) { for (Object service : keySet) {
ClipboardContentProviderService service = (ClipboardContentProviderService) name; if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
if (service instanceof CodeBrowserClipboardProvider) { return (ClipboardContentProviderService) service;
return service;
} }
} }
return null; return null;

View file

@ -58,7 +58,6 @@ public class ByteViewerClipboardProvider extends ByteCopier
PluginTool tool) { PluginTool tool) {
this.provider = provider; this.provider = provider;
this.tool = tool; this.tool = tool;
currentProgram = provider.getProgram();
} }
@Override @Override
@ -80,11 +79,6 @@ public class ByteViewerClipboardProvider extends ByteCopier
@Override @Override
public boolean paste(Transferable pasteData) { public boolean paste(Transferable pasteData) {
if (!supportsPasteTransferable(pasteData)) {
tool.setStatusInfo("Paste failed: No valid data on clipboard", true);
return false;
}
try { try {
// try the default paste // try the default paste
return pasteBytes(pasteData); return pasteBytes(pasteData);
@ -106,42 +100,17 @@ public class ByteViewerClipboardProvider extends ByteCopier
@Override @Override
public Transferable copy(TaskMonitor monitor) { public Transferable copy(TaskMonitor monitor) {
String byteString = copyBytesAsString(currentSelection, true, monitor); String byteString = copyBytesAsString(currentSelection, true, monitor);
String textSelection = provider.getCurrentTextSelection(); String textSelection = getTextSelection();
return new ByteViewerTransferable(byteString, textSelection); return new ByteStringTransferable(byteString, textSelection);
}
protected String getTextSelection() {
return provider.getCurrentTextSelection();
} }
@Override @Override
public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor) { public Transferable copySpecial(ClipboardType copyType, TaskMonitor monitor) {
return copyBytes(copyType, monitor);
String byteString = null;
if (copyType == BYTE_STRING_TYPE) {
byteString = copyBytesAsString(currentSelection, true, monitor);
}
else if (copyType == BYTE_STRING_NO_SPACE_TYPE) {
byteString = copyBytesAsString(currentSelection, false, monitor);
}
else if (copyType == PYTHON_BYTE_STRING_TYPE) {
byteString = "b'"
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", "\\x") + "'";
}
else if (copyType == PYTHON_LIST_TYPE) {
byteString = "[ "
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " ]";
}
else if (copyType == CPP_BYTE_ARRAY_TYPE) {
byteString = "{ "
+ copyBytesAsString(currentSelection, true, monitor).replaceAll(" ", ", 0x") + " }";
}
else {
return null;
}
return new ByteViewerTransferable(byteString);
}
void setSelection(ProgramSelection selection) {
currentSelection = selection;
updateEnablement();
} }
private void updateEnablement() { private void updateEnablement() {
@ -153,6 +122,11 @@ public class ByteViewerClipboardProvider extends ByteCopier
currentLocation = location; currentLocation = location;
} }
void setSelection(ProgramSelection selection) {
currentSelection = selection;
updateEnablement();
}
void setProgram(Program p) { void setProgram(Program p) {
currentProgram = p; currentProgram = p;
currentLocation = null; currentLocation = null;

View file

@ -0,0 +1,239 @@
/* ###
* 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.byteviewer;
import static org.hamcrest.core.IsInstanceOf.*;
import static org.junit.Assert.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.OptionDialog;
import ghidra.app.util.ByteCopier;
import ghidra.app.util.ByteCopier.ByteStringTransferable;
import ghidra.app.util.ByteCopier.ProgrammingByteStringTransferable;
import ghidra.app.util.ClipboardType;
import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.DummyTool;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.AssertException;
import ghidra.util.task.TaskMonitor;
public class ByteViewerClipboardProviderTest extends AbstractGhidraHeadedIntegrationTest {
private Program program;
private ByteViewerClipboardProvider clipboardProvider;
@Before
public void setUp() throws Exception {
program = createProgram();
PluginTool tool = new DummyTool() {
@Override
public boolean execute(Command command, DomainObject obj) {
boolean result = command.applyTo(obj);
if (!result) {
throw new AssertException("Failed to write bytes");
}
return true;
}
};
clipboardProvider = new ByteViewerClipboardProvider(null, tool) {
// overridden to stub this method, since we don't have a real provider
@Override
protected String getTextSelection() {
return null;
}
};
clipboardProvider.setProgram(program);
}
private Program createProgram() throws Exception {
ProgramBuilder builder = new ProgramBuilder("default", ProgramBuilder._TOY, this);
builder.createMemory("test", "0x01001050", 20000);
builder.setBytes("0x01001050",
"0e 5e f4 77 33 58 f4 77 91 45 f4 77 88 7c f4 77 8d 70 f5 77 05 62 f4 77 f0 a3 " +
"f4 77 09 56 f4 77 10 17 f4 77 f7 29 f6 77 02 59 f4 77");
builder.setBytes("0x01002050", "00 00 00 00 00 00 00 00 00 00 00 00 00");
builder.createMemoryReference("0x01002cc0", "0x01002cf0", RefType.DATA,
SourceType.USER_DEFINED);
builder.createMemoryReference("0x01002d04", "0x01002d0f", RefType.DATA,
SourceType.USER_DEFINED);
DataType dt = DataType.DEFAULT;
Parameter p = new ParameterImpl(null, dt, builder.getProgram());
builder.createEmptyFunction("ghidra", "0x01002cf5", 1, dt, p);
builder.createEmptyFunction("sscanf", "0x0100415a", 1, dt, p);
builder.setBytes("0x0100418c", "ff 15 08 10 00 01");
builder.disassemble("0x0100418c", 6);
return builder.getProgram();
}
@Test
public void testCopyPasteSpecial_ByteString() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", length));
ClipboardType type = ByteCopier.BYTE_STRING_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("0e 5e f4 77", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPasteSpecial_ByteStringNoSpaces() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", length));
ClipboardType type = ByteCopier.BYTE_STRING_NO_SPACE_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("0e5ef477", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPasteSpecial_PythonByteString() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", length));
ClipboardType type = ByteCopier.PYTHON_BYTE_STRING_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("b'\\x0e\\x5e\\xf4\\x77'", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPasteSpecial_PythonListString() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", 4));
ClipboardType type = ByteCopier.PYTHON_LIST_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("[ 0x0e, 0x5e, 0xf4, 0x77 ]", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
@Test
public void testCopyPasteSpecial_CppByteArray() throws Exception {
int length = 4;
clipboardProvider.setSelection(selection("0x01001050", 4));
ClipboardType type = ByteCopier.CPP_BYTE_ARRAY_TYPE;
Transferable transferable = clipboardProvider.copySpecial(type, TaskMonitor.DUMMY);
assertThat(transferable, instanceOf(ProgrammingByteStringTransferable.class));
String byteString = (String) transferable.getTransferData(DataFlavor.stringFlavor);
assertEquals("{ 0x0e, 0x5e, 0xf4, 0x77 }", byteString);
String pasteAddress = "0x01002050";
paste(pasteAddress, transferable);
assertBytesAt(pasteAddress, "0e 5e f4 77", length);
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void paste(String address, Transferable transferable) {
tx(program, () -> {
doPaste(address, transferable);
});
}
private void doPaste(String address, Transferable transferable) {
clipboardProvider.setLocation(location(address));
runSwing(() -> clipboardProvider.paste(transferable), false);
OptionDialog confirmDialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(confirmDialog, "Yes");
waitForTasks();
program.flushEvents();
waitForSwing();
}
private void assertBytesAt(String address, String bytes, int length)
throws MemoryAccessException {
Memory memory = program.getMemory();
byte[] memoryBytes = new byte[length];
memory.getBytes(addr(address), memoryBytes, 0, length);
String memoryByteString = NumericUtilities.convertBytesToString(memoryBytes, " ");
assertEquals(bytes, memoryByteString);
}
private ProgramSelection selection(String addressString, int n) {
Address address = addr(addressString);
AddressSetView addresses = new AddressSet(address, address.add(n - 1));
return new ProgramSelection(addresses);
}
private Address addr(String addr) {
return program.getAddressFactory().getAddress(addr);
}
private ProgramLocation location(String addressString) {
return new ProgramLocation(program, addr(addressString));
}
}

View file

@ -16,16 +16,22 @@
package help.screenshot; package help.screenshot;
import java.awt.*; import java.awt.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.*; import javax.swing.*;
import org.junit.Test; import org.junit.Test;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import ghidra.app.plugin.core.clipboard.CopyPasteSpecialDialog; import ghidra.app.plugin.core.clipboard.*;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.services.ClipboardContentProviderService;
import ghidra.app.util.viewer.field.MnemonicFieldFactory; import ghidra.app.util.viewer.field.MnemonicFieldFactory;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -33,10 +39,6 @@ import ghidra.util.exception.AssertException;
public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator { public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
public ClipboardPluginScreenShots() {
super();
}
@Test @Test
public void testCaptureCopySpecial() { public void testCaptureCopySpecial() {
@ -47,9 +49,12 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
makeSelection(0x401000, 0x401000); makeSelection(0x401000, 0x401000);
sel = cb.getCurrentSelection(); sel = cb.getCurrentSelection();
Msg.debug(this, "selection: " + sel);
showCopySpecialDialog(); CopyPasteSpecialDialog dialog = showCopySpecialDialog();
Window window = SwingUtilities.windowForComponent(dialog.getComponent());
Dimension size = window.getSize();
setWindowSize(window, size.width, 330);
captureDialog(); captureDialog();
} }
@ -184,7 +189,39 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
crop(imageBounds); crop(imageBounds);
} }
private void showCopySpecialDialog() { private CopyPasteSpecialDialog showCopySpecialDialog() {
performAction("Copy Special", "ClipboardPlugin", false); ClipboardPlugin plugin = getPlugin(tool, ClipboardPlugin.class);
ClipboardContentProviderService service = getClipboardService(plugin);
DockingActionIf pasteAction = getLocalAction(service, "Copy Special", plugin);
performAction(pasteAction, false);
return waitForDialogComponent(CopyPasteSpecialDialog.class);
}
private ClipboardContentProviderService getClipboardService(
ClipboardPlugin clipboardPlugin) {
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
Set<?> keySet = serviceMap.keySet();
for (Object name : keySet) {
ClipboardContentProviderService service = (ClipboardContentProviderService) name;
if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
return service;
}
}
return null;
}
@SuppressWarnings("unchecked")
private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName,
ClipboardPlugin clipboardPlugin) {
Map<?, ?> actionsByService =
(Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
List<DockingAction> actionList = (List<DockingAction>) actionsByService.get(service);
for (DockingAction pluginAction : actionList) {
if (pluginAction.getName().equals(actionName)) {
return pluginAction;
}
}
return null;
} }
} }

View file

@ -0,0 +1,36 @@
/* ###
* 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.clipboard;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import ghidra.app.plugin.core.byteviewer.ByteViewerClipboardProviderTest;
//@formatter:off
@RunWith(Suite.class)
@SuiteClasses({
ClipboardPluginTest.class,
ByteViewerClipboardProviderTest.class,
CodeBrowserClipboardProviderTest.class,
CopyPasteCommentsTest.class,
CopyPasteFunctionInfoTest.class
})
public class CopyPasteTestSuite {
// in the annotation
}
//@formatter:on