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
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>
</UL>
@ -208,6 +217,17 @@
<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>
<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>
<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)) {
return false;
}
return clipboardService.canCopy();
}
@ -413,7 +412,8 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
}
Clipboard systemClipboard = getSystemClipboard();
return clipboardService.canPaste(getAvailableDataFlavors(systemClipboard));
DataFlavor[] flavors = getAvailableDataFlavors(systemClipboard);
return clipboardService.canPaste(flavors);
}
@Override
@ -444,7 +444,6 @@ public class ClipboardPlugin extends ProgramPlugin implements ClipboardOwner, Cl
if (!clipboardService.isValidContext(context)) {
return false;
}
return clipboardService.canCopySpecial();
}

View file

@ -48,6 +48,7 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
public class CodeBrowserClipboardProvider extends ByteCopier
implements ClipboardContentProviderService {
@ -85,20 +86,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
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 ComponentProvider componentProvider;
private ListingModel model;
@ -157,10 +144,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
else if (element.equals(COMMENTS_TYPE.getFlavor())) {
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)) {
return pasteLabelString(pasteData);
}
@ -169,10 +152,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
}
}
// last ditch effort, try to paste as a byte string
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor);
if (string != null) {
return pasteByteString(string);
if (super.pasteBytes(pasteData)) {
return true;
}
tool.setStatusInfo("Paste failed: unsupported data type", true);
@ -181,36 +162,14 @@ public class CodeBrowserClipboardProvider extends ByteCopier
String msg = e.getMessage();
if (msg == null) {
msg = e.toString();
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
}
Msg.error(this, "Unexpected Exception: " + msg, e);
tool.setStatusInfo("Paste failed: " + msg, true);
}
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
public List<ClipboardType> getCurrentCopyTypes() {
return COPY_TYPES;
@ -234,45 +193,8 @@ public class CodeBrowserClipboardProvider extends ByteCopier
else if (copyType == COMMENTS_TYPE) {
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;
}
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();
return copyBytes(copyType, monitor);
}
public void setStringContent(String text) {
@ -287,6 +209,12 @@ public class CodeBrowserClipboardProvider extends ByteCopier
currentLocation = location;
}
public void setSelection(ProgramSelection selection) {
currentSelection = selection;
copyFromSelectionEnabled = selection != null && !selection.isEmpty();
notifyStateChanged();
}
public void setProgram(Program p) {
currentProgram = p;
currentLocation = null;
@ -323,11 +251,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
}
else if (currentLocation instanceof BytesFieldLocation) {
// 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);
}
else if (currentLocation instanceof OperandFieldLocation) {
@ -439,7 +362,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier
private Transferable copyByteString(Address 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) {
@ -451,13 +374,13 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return new CodeUnitInfoTransferable(list);
}
@SuppressWarnings("unchecked") // assumed correct data; handled in exception case
private boolean pasteLabelsComments(Transferable pasteData, boolean pasteLabels,
boolean pasteComments) {
try {
List<CodeUnitInfo> list = (List<CodeUnitInfo>) pasteData.getTransferData(
List<?> list = (List<?>) pasteData.getTransferData(
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);
return tool.execute(cmd, currentProgram);
}
@ -494,7 +417,15 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return tool.execute(cmd, currentProgram);
}
else if (currentLocation instanceof OperandFieldLocation) {
OperandFieldLocation operandLocation = (OperandFieldLocation) currentLocation;
return pasteOperandField((OperandFieldLocation) currentLocation, labelName);
}
// try pasting onto something that is not a label
return maybePasteNonLabelString(labelName);
}
private boolean pasteOperandField(OperandFieldLocation operandLocation, String labelName) {
int opIndex = operandLocation.getOperandIndex();
Listing listing = currentProgram.getListing();
Instruction instruction = listing.getInstructionAt(operandLocation.getAddress());
@ -524,7 +455,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier
namespace, SourceType.USER_DEFINED);
return tool.execute(cmd, currentProgram);
}
}
// try pasting onto something that is not a label
return maybePasteNonLabelString(labelName);
@ -707,8 +637,7 @@ public class CodeBrowserClipboardProvider extends ByteCopier
return true;
}
if (flavor.equals(DataFlavor.stringFlavor)) {
// TODO: check if it is a valid hex string...
return true;
return true; // check if it is a valid hex string?
}
}
}

View file

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

View file

@ -18,6 +18,8 @@ package ghidra.app.util;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import docking.dnd.GenericDataFlavor;
import docking.dnd.StringTransferable;
@ -25,8 +27,7 @@ import docking.widgets.OptionDialog;
import ghidra.framework.cmd.Command;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
@ -64,6 +65,19 @@ public abstract class ByteCopier {
public static final ClipboardType CPP_BYTE_ARRAY_TYPE =
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() {
try {
@ -72,7 +86,7 @@ public abstract class ByteCopier {
"Local flavor--byte string with spaces");
}
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);
}
@ -87,7 +101,7 @@ public abstract class ByteCopier {
"Local flavor--byte string with NO spaces");
}
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);
}
@ -102,7 +116,7 @@ public abstract class ByteCopier {
"Local flavor--Python byte string");
}
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);
}
@ -117,7 +131,7 @@ public abstract class ByteCopier {
"Local flavor--Python list");
}
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);
}
@ -132,7 +146,7 @@ public abstract class ByteCopier {
"Local flavor--C++ array");
}
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);
}
@ -148,8 +162,12 @@ public abstract class ByteCopier {
// limit construction
}
protected Transferable copyBytes(boolean includeSpaces, TaskMonitor monitor) {
return copyBytes(currentSelection, includeSpaces, monitor);
protected AddressSetView getSelectedAddresses() {
AddressSetView addressSet = currentSelection;
if (addressSet == null || addressSet.isEmpty()) {
return new AddressSet(currentLocation.getAddress());
}
return currentSelection;
}
protected Transferable copyBytes(AddressSetView addresses, boolean includeSpaces,
@ -160,42 +178,18 @@ public abstract class ByteCopier {
protected String copyBytesAsString(AddressSetView addresses, boolean includeSpaces,
TaskMonitor monitor) {
Memory memory = currentProgram.getMemory();
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);
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) {
byte[] bytes = getHexBytes(transferString);
@ -271,27 +265,121 @@ public abstract class ByteCopier {
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)
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;
}
if (pasteData.isDataFlavorSupported(BYTE_STRING_FLAVOR)) {
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);
}
// see if the pasted data is similar to other known programming formats
String string = (String) pasteData.getTransferData(DataFlavor.stringFlavor);
if (string == null) {
tool.setStatusInfo("Paste failed: no string data", true);
return false;
}
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) {
Command cmd = new Command() {
@ -299,76 +387,101 @@ public abstract class ByteCopier {
@Override
public boolean applyTo(DomainObject domainObject) {
if (domainObject instanceof Program) {
if (!(domainObject instanceof Program)) {
return false;
}
String validString = string;
if (!isOnlyAsciiBytes(string)) {
tool.setStatusInfo("Pasted string contained non-text ascii bytes. " +
"Only the ascii text was pasted.", true);
"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)";
status = "Improper data format. Expected sequence of hex bytes";
tool.beep();
return false;
}
// Ensure that we are not writing over instructions
Program curProgram = (Program) domainObject;
Listing listing = curProgram.getListing();
Address curAddr = currentLocation.getAddress();
int byteCount = bytes.length;
for (int i = 0; i < byteCount;) {
if (curAddr == null) {
status = "Not enough addresses to paste bytes";
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;
}
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();
}
// Per SCR 11212, 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.
// 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;
}
private boolean confirmPaste(String validString) {
// 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;
return result != OptionDialog.NO_OPTION;
}
// 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;
}
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();
return false;
}
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
if (!(codeUnit instanceof Data) || ((Data) codeUnit).isDefined()) {
status = "Cannot paste on top of defined instructions/data";
tool.beep();
return false;
}
int length = codeUnit.getLength();
i += length;
address = codeUnit.getMaxAddress().next();
}
return true;
}
@Override
public String getStatusMsg() {
@ -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(),
BYTE_STRING_TYPE.getFlavor(), PYTHON_BYTE_STRING_TYPE.getFlavor(),
PYTHON_LIST_TYPE.getFlavor(), CPP_BYTE_ARRAY_TYPE.getFlavor(),
DataFlavor.stringFlavor };
private final List<DataFlavor> flavorList = Arrays.asList(flavors);
private List<DataFlavor> flavorList;
private DataFlavor[] flavors;
private DataFlavor programmingFlavor;
private String byteString;
private final String byteString;
private final String byteViewerRepresentation;
public ByteViewerTransferable(String byteString) {
this(byteString, null);
}
public ByteViewerTransferable(String byteString, String byteViewerRepresentation) {
public ProgrammingByteStringTransferable(String byteString, DataFlavor flavor) {
this.byteString = byteString;
this.byteViewerRepresentation = byteViewerRepresentation;
this.programmingFlavor = flavor;
this.flavors = new DataFlavor[] { flavor, DataFlavor.stringFlavor };
this.flavorList = Arrays.asList(flavors);
}
@Override
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (flavor.equals(DataFlavor.stringFlavor)) {
if (byteViewerRepresentation != null) {
return byteViewerRepresentation;
return byteString; // just default to the byte string when no 'special' string data
}
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
}
@ -481,15 +630,6 @@ public abstract class ByteCopier {
if (flavor.equals(BYTE_STRING_NO_SPACE_TYPE.getFlavor())) {
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);
}

View file

@ -1,6 +1,5 @@
/* ###
* 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.
@ -37,22 +36,21 @@ public class ClipboardType {
}
/**
* Returns the DataFlavor for this ClipboardType
* Returns the DataFlavor for this type
* @return the flavor
*/
public DataFlavor getFlavor() {
return flavor;
}
/**
* Returns the name of this Clipboard Type.
* Returns the name of this type
* @return the name
*/
public String getTypeName() {
return typeName;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
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);
programTwo = (ProgramDB) pmTwo.getCurrentProgram();
});
}
@Test
@ -145,7 +146,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
goTo(toolOne, 0x32a);
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());
}
@ -194,7 +196,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
// in Program One, add MyLabel at 032a
int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x032a), "MyLabel",
programOne.getSymbolTable()
.createLabel(addr(programOne, 0x032a), "MyLabel",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true);
@ -385,7 +388,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
programOne.getSymbolTable().getSymbol("LAB_0331", addr(programOne, 0x0331), null);
// in Browser(1) change default label at 331 to JUNK
int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x0331), "JUNK",
programOne.getSymbolTable()
.createLabel(addr(programOne, 0x0331), "JUNK",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true);
//
@ -426,7 +430,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
public void testPasteAtMultipleLabels() throws Exception {
// in program 2, create a second label, JUNK2, at 0331
int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2",
programOne.getSymbolTable()
.createLabel(addr(programOne, 0x331), "JUNK2",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true);
@ -474,7 +479,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
@Test
public void testPasteWhereUserLabelExists() throws Exception {
int transactionID = programOne.startTransaction("test");
programOne.getSymbolTable().createLabel(addr(programOne, 0x331), "JUNK2",
programOne.getSymbolTable()
.createLabel(addr(programOne, 0x331), "JUNK2",
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true);
@ -586,13 +592,19 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
cb.goToField(addr(programOne, 0x0331), LabelFieldFactory.FIELD_NAME, 0, 0);
f = (ListingTextField) cb.getCurrentField();
assertEquals(programOne.getSymbolTable().getSymbol("LAB_00000331", addr(programOne, 0x0331),
null).getName(), f.getText());
assertEquals(programOne.getSymbolTable()
.getSymbol("LAB_00000331", addr(programOne, 0x0331),
null)
.getName(),
f.getText());
cb.goToField(addr(programOne, 0x031b), LabelFieldFactory.FIELD_NAME, 0, 0);
f = (ListingTextField) cb.getCurrentField();
assertEquals(programOne.getSymbolTable().getSymbol("LAB_0000031b", addr(programOne, 0x031b),
null).getName(), f.getText());
assertEquals(programOne.getSymbolTable()
.getSymbol("LAB_0000031b", addr(programOne, 0x031b),
null)
.getName(),
f.getText());
redo(programOne);
@ -616,7 +628,8 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
// create a function over the range 0x31b through 0x0343.
int transactionID = programOne.startTransaction("test");
String name = SymbolUtilities.getDefaultFunctionName(min);
programOne.getListing().createFunction(name, min, new AddressSet(min, max),
programOne.getListing()
.createFunction(name, min, new AddressSet(min, max),
SourceType.USER_DEFINED);
programOne.endTransaction(transactionID, true);
programOne.flushEvents();
@ -710,19 +723,23 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
private void copyToolTwoLabels() {
ClipboardPlugin plugin = getPlugin(toolTwo, ClipboardPlugin.class);
ClipboardContentProviderService service =
getCodeBrowserClipboardContentProviderService(plugin);
getClipboardService(plugin);
DockingAction action = getLocalAction(service, "Copy Special", plugin);
assertNotNull(action);
assertEnabled(action, cb2.getProvider());
plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE);
runSwing(
() -> plugin.copySpecial(service, CodeBrowserClipboardProvider.LABELS_COMMENTS_TYPE));
}
private void pasteToolOne() {
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());
performAction(pasteAction, true);
waitForSwing();
}
private void setupTool(PluginTool tool) throws Exception {
@ -809,13 +826,13 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
waitForSwing();
}
private ClipboardContentProviderService getCodeBrowserClipboardContentProviderService(
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 instanceof CodeBrowserClipboardProvider) {
if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
return service;
}
}
@ -825,26 +842,23 @@ public class CopyPasteCommentsTest extends AbstractProgramBasedTest {
@SuppressWarnings("unchecked")
private DockingAction getLocalAction(ClipboardContentProviderService service, String actionName,
ClipboardPlugin clipboardPlugin) {
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
Set<?> keySet = serviceMap.keySet();
for (Object name : keySet) {
ClipboardContentProviderService currentService = (ClipboardContentProviderService) name;
if (currentService == service) {
List<DockingAction> actionList = (List<DockingAction>) serviceMap.get(service);
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;
}
private void assertEnabled(DockingActionIf action, ComponentProvider provider) {
boolean isEnabled =
runSwing(() -> action.isEnabledForContext(provider.getActionContext(null)));
assertTrue(isEnabled);
runSwing(() -> {
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.awt.Point;
import java.awt.event.MouseEvent;
@ -26,6 +25,7 @@ import javax.swing.SwingUtilities;
import org.junit.*;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
import docking.widgets.fieldpanel.FieldPanel;
import ghidra.app.cmd.function.SetVariableCommentCmd;
@ -53,11 +53,8 @@ import ghidra.program.util.ProgramSelection;
import ghidra.test.*;
/**
* Test copy/paste function information.
*
*
* Test copy/paste function information
*/
public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
@ -72,14 +69,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
private Options fieldOptions2;
private CodeBrowserPlugin cb1;
/**
* Constructor for CopyPasteFunctionInfoTest.
* @param arg0
*/
public CopyPasteFunctionInfoTest() {
super();
}
private Program buildNotepad(String name) throws Exception {
ToyProgramBuilder builder = new ToyProgramBuilder(name, true, ProgramBuilder._TOY);
builder.createMemory("test1", "0x01001000", 0x8000);
@ -133,9 +122,6 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
resetOptions();
}
/*
* @see TestCase#tearDown()
*/
@After
public void tearDown() throws Exception {
env.dispose();
@ -174,12 +160,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, 0x1004700);
click2();
// paste
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
paste(toolTwo);
// function FUN_01004700 should be renamed to "ghidra"
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
@ -219,12 +200,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, entryAddr);
click2();
// paste
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
paste(toolTwo);
CodeBrowserPlugin cb = getPlugin(toolTwo, CodeBrowserPlugin.class);
cb.goToField(entryAddr, PlateFieldFactory.FIELD_NAME, 0, 0);
@ -283,12 +259,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, addr);
click2();
// paste
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
paste(toolTwo);
// verify the code browser field shows the comment
func = programTwo.getListing().getFunctionAt(addr);
@ -338,12 +309,8 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
addr = getAddr(programTwo, 0x01004260);
goToAddr(toolTwo, addr);
click2();
// paste
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
paste(toolTwo);
// verify the code browser field shows the comment
func = programTwo.getListing().getFunctionAt(addr);
@ -416,12 +383,7 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
goToAddr(toolTwo, 0x0100176f);
click2();
// paste
plugin = getPlugin(toolTwo, ClipboardPlugin.class);
DockingActionIf pasteAction = getAction(plugin, "Paste");
assertEnabled(pasteAction);
performAction(pasteAction, true);
waitForSwing();
paste(toolTwo);
addr = getAddr(programTwo, 0x0100176f);
// nothing should happen with the stack variable comments
@ -433,7 +395,36 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
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 {
tool.addPlugin(ClipboardPlugin.class.getName());
@ -511,10 +502,9 @@ public class CopyPasteFunctionInfoTest extends AbstractGhidraHeadedIntegrationTe
ClipboardPlugin clipboardPlugin) {
Map<?, ?> serviceMap = (Map<?, ?>) getInstanceField("serviceActionMap", clipboardPlugin);
Set<?> keySet = serviceMap.keySet();
for (Object name : keySet) {
ClipboardContentProviderService service = (ClipboardContentProviderService) name;
if (service instanceof CodeBrowserClipboardProvider) {
return service;
for (Object service : keySet) {
if (service.getClass().equals(CodeBrowserClipboardProvider.class)) {
return (ClipboardContentProviderService) service;
}
}
return null;

View file

@ -58,7 +58,6 @@ public class ByteViewerClipboardProvider extends ByteCopier
PluginTool tool) {
this.provider = provider;
this.tool = tool;
currentProgram = provider.getProgram();
}
@Override
@ -80,11 +79,6 @@ public class ByteViewerClipboardProvider extends ByteCopier
@Override
public boolean paste(Transferable pasteData) {
if (!supportsPasteTransferable(pasteData)) {
tool.setStatusInfo("Paste failed: No valid data on clipboard", true);
return false;
}
try {
// try the default paste
return pasteBytes(pasteData);
@ -106,42 +100,17 @@ public class ByteViewerClipboardProvider extends ByteCopier
@Override
public Transferable copy(TaskMonitor monitor) {
String byteString = copyBytesAsString(currentSelection, true, monitor);
String textSelection = provider.getCurrentTextSelection();
return new ByteViewerTransferable(byteString, textSelection);
String textSelection = getTextSelection();
return new ByteStringTransferable(byteString, textSelection);
}
protected String getTextSelection() {
return provider.getCurrentTextSelection();
}
@Override
public Transferable copySpecial(ClipboardType copyType, TaskMonitor 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();
return copyBytes(copyType, monitor);
}
private void updateEnablement() {
@ -153,6 +122,11 @@ public class ByteViewerClipboardProvider extends ByteCopier
currentLocation = location;
}
void setSelection(ProgramSelection selection) {
currentSelection = selection;
updateEnablement();
}
void setProgram(Program p) {
currentProgram = p;
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;
import java.awt.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.*;
import org.junit.Test;
import docking.DialogComponentProvider;
import docking.action.DockingAction;
import docking.action.DockingActionIf;
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.CodeViewerProvider;
import ghidra.app.services.ClipboardContentProviderService;
import ghidra.app.util.viewer.field.MnemonicFieldFactory;
import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg;
@ -33,10 +39,6 @@ import ghidra.util.exception.AssertException;
public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
public ClipboardPluginScreenShots() {
super();
}
@Test
public void testCaptureCopySpecial() {
@ -47,9 +49,12 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
makeSelection(0x401000, 0x401000);
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();
}
@ -184,7 +189,39 @@ public class ClipboardPluginScreenShots extends GhidraScreenShotGenerator {
crop(imageBounds);
}
private void showCopySpecialDialog() {
performAction("Copy Special", "ClipboardPlugin", false);
private CopyPasteSpecialDialog showCopySpecialDialog() {
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