diff --git a/Ghidra/Features/Base/ghidra_scripts/PasteCopiedListingBytesScript.java b/Ghidra/Features/Base/ghidra_scripts/PasteCopiedListingBytesScript.java
index ae15cdf5a3..29f4da750c 100644
--- a/Ghidra/Features/Base/ghidra_scripts/PasteCopiedListingBytesScript.java
+++ b/Ghidra/Features/Base/ghidra_scripts/PasteCopiedListingBytesScript.java
@@ -4,21 +4,26 @@
* 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.
*/
-//Useful for getting bytes into a program that have been copied and pasted
-//as text onto a website or other text documents. If there is no program open
-//when the script is run, you will be prompted to select a processor and a
-//new empty program will be created. Text in the copy buffer will be parsed
-//to extract address and bytes, and everything else will be ignored.
-//Example listing text:
+// Useful for getting bytes into a program that have been copied and pasted
+// as text onto a website or other text documents. The bytes can be from a listing
+// or from a hexdump.
+//
+// If there is no program open when the script is run, you will be prompted
+// to select a processor and a new empty program will be created.
+//
+// Text in the clipboard will be parsed
+// to extract address and bytes, and everything else will be ignored.
+//
+// Example listing text:
// LAB_0007aaca XREF[1]:
// 0007aac0(j)
// 0007aaca 01 24
@@ -30,20 +35,33 @@
//
// 0007aad6 1f d0
// beq LAB_0007ab18
+//
+// Example hexdump text:
+// Hex dump of section '.text':
+// NOTE: This section has relocations against it, but these have NOT been applied to this dump.
+// 0x00000000 80b487b0 00aff860 c7e90023 3b683b61 .......`...#;h;a
+// 0x00000010 b7f92030 7b61fb68 1a4607f1 100393e8 .. 0{a.h.F......
+// ...
+// 0x00000210 1a443b6a 13441846 1437bd46 5df8047b .D;j.D.F.7.F]..{
+//
//@category Program
//@menupath Edit.Paste Listing Text
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.*;
+import java.util.Map.Entry;
import docking.dnd.GClipboard;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.ProgramManager;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.*;
+import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
public class PasteCopiedListingBytesScript extends GhidraScript {
+
+ @Override
public void run() throws Exception {
int id = 0;
if (currentProgram == null) {
@@ -57,18 +75,20 @@ public class PasteCopiedListingBytesScript extends GhidraScript {
Memory memory = currentProgram.getMemory();
// get data from the clip board and turn it into a string
- String ClipboardText = retrieveClipBoardText();
- if (ClipboardText == null) {
+ String clipBoardText = retrieveClipBoardText();
+ if (clipBoardText == null) {
println("Nothing is copied to your clip board");
return;
}
// evaluate the copy buffer and get the byte array
- Map
bytesToAdd = parseListingStringToByte(ClipboardText);
+ Map bytesToAdd = parseListingStringToByte(clipBoardText);
if (bytesToAdd.isEmpty()) {
println("There are no bytes copied to your clip board");
return;
}
+
+ coalesceBytes(bytesToAdd);
// Check if memory block with the byte+address exists
boolean exists = checkForExistingMemory(memory, bytesToAdd);
@@ -97,6 +117,35 @@ public class PasteCopiedListingBytesScript extends GhidraScript {
println("Created " + getNeededAddressSet(bytesToAdd));
}
+ private void coalesceBytes(Map bytesToAdd) {
+ // Map is assumed to be a sorted Map
+ Set> entrySet = bytesToAdd.entrySet();
+ Iterator> iterator = entrySet.iterator();
+ Map.Entry entryA = iterator.next();
+ while (entryA != null && iterator.hasNext()) {
+ Map.Entry entryB = iterator.next();
+ Address addrA = entryA.getKey();
+ Address addrB = entryB.getKey();
+ byte bytesA[] = entryA.getValue();
+ if (addrA.add(bytesA.length).equals(addrB)) {
+ byte bytesB[] = entryB.getValue();
+ // coalesce, and res-start iterator
+ byte concatBytes[] = Arrays.copyOf(bytesA,bytesA.length+bytesB.length);
+ System.arraycopy(bytesB, 0, concatBytes, bytesA.length, bytesB.length);
+
+ bytesToAdd.replace(addrA, concatBytes);
+ bytesToAdd.remove(addrB);
+ iterator = entrySet.iterator();
+ entryA = null;
+ if (iterator.hasNext()) {
+ entryA = iterator.next();
+ }
+ continue;
+ }
+ entryA = entryB;
+ }
+ }
+
private void createMissingMemory(Map bytesToAdd, Memory memory)
throws CancelledException, Exception {
AddressSet neededMem = getNeededAddressSet(bytesToAdd);
@@ -151,44 +200,85 @@ public class PasteCopiedListingBytesScript extends GhidraScript {
private Map parseListingStringToByte(String ClipboardText)
throws CancelledException {
+ // TreeMap so the entries will be sorted by Address
+ Map newMap = new TreeMap();
String[] bufferLines = ClipboardText.split("\n");
- Map newMap = new HashMap();
+
+ Address firstAddress = null;
+
+ // For each line, look for address and bytes, accumlate address/byteStrings
+ // in a list, throwing out any text that can't be parsed
for (String line : bufferLines) {
monitor.checkCancelled();
line = line.trim();
+ if (line.isEmpty()) {
+ continue;
+ }
String[] words = line.split(" ");
+ if (words.length == 0) {
+ continue;
+ }
String startOfLine = words[0];
- Address firstAddress = toAddr(startOfLine);
+ // if start of line word is > 2 assume address
+ // other wise, consider it a continuation of the
+ // previous address
+ // 001325a4 03 00 0b0 sethi %hi(0x1000),g1
+ // 00 04
+
+ boolean skipFirstWord = false;
+ if (startOfLine.length() > 2) {
+ firstAddress = toAddr(startOfLine);
+ skipFirstWord = true;
+ }
if (firstAddress == null) {
continue;
}
- List bytesFound = new ArrayList();
+ List bytesStringsList = new ArrayList();
+ int numBytesFound = 0;
for (String word : words) {
monitor.checkCancelled();
- if (word == words[0]) {
+ if (skipFirstWord) {
+ skipFirstWord = false;
continue;
}
- if (word.isBlank() || word.length() > 2) {
+ // break if bytes already found and separator more than one " "
+ // 001325a4 03 00 0b0 sethi %hi(0x1000),g1
+ if (numBytesFound > 0 && word.isBlank()) {
+ break;
+ }
+ int len = word.length();
+ if (word.isBlank() || len > 8 || (len % 2) != 0) {
break;
}
try {
- Integer.parseInt(word, 16);
+ Long.parseLong(word, 16);
}
catch (Exception e) {
break;
}
- bytesFound.add(word);
-
- }
- byte[] newBytes = new byte[bytesFound.size()];
- int i = 0;
- for (String byteString : bytesFound) {
- monitor.checkCancelled();
- byte bVal = (byte) Integer.parseInt(byteString, 16);
- newBytes[i++] = bVal;
+ bytesStringsList.add(word);
+ numBytesFound += len / 2;
}
+
+ // parse found address/byteStrings into byte array
+ byte newBytes[] = parseHexStrings(bytesStringsList,numBytesFound);
newMap.put(firstAddress, newBytes);
+ firstAddress = firstAddress.add(newBytes.length);
}
return newMap;
}
+
+ private byte[] parseHexStrings(List byteStringsList, int numBytesFound)
+ throws CancelledException {
+ byte[] newBytes = new byte[numBytesFound];
+ int byteArrayIndex = 0;
+ for (String byteString : byteStringsList) {
+ monitor.checkCancelled();
+ int numBytes = byteString.length() / 2;
+ byte[] bytes = NumericUtilities.convertStringToBytes(byteString);
+ System.arraycopy(bytes, 0, newBytes, byteArrayIndex, bytes.length);
+ byteArrayIndex += bytes.length;
+ }
+ return newBytes;
+ }
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/PasteCopiedListingBytesScriptTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/PasteCopiedListingBytesScriptTest.java
new file mode 100644
index 0000000000..d6b50b6bd5
--- /dev/null
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/PasteCopiedListingBytesScriptTest.java
@@ -0,0 +1,332 @@
+/* ###
+ * 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.script;
+
+import static org.junit.Assert.*;
+
+import java.awt.datatransfer.*;
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.*;
+
+import docking.dnd.GClipboard;
+import ghidra.framework.Application;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.test.*;
+
+/**
+ * Tests the {@code PasteCopiedListingBytesScript}, which grabs the copy buffer, and
+ * attempts to parse out address/bytes from either listing format or hexdump format.
+ */
+public class PasteCopiedListingBytesScriptTest extends AbstractGhidraHeadedIntegrationTest {
+
+ private TestEnv env;
+ private File script;
+
+ private Program program;
+
+ @Before
+ public void setUp() throws Exception {
+
+ program = buildProgram();
+
+ env = new TestEnv();
+ env.launchDefaultTool(program);
+
+ String scriptPath = "ghidra_scripts/PasteCopiedListingBytesScript.java";
+ script = Application.getModuleFile("Base", scriptPath).getFile(true);
+ }
+
+ @After
+ public void tearDown() {
+ env.dispose();
+ }
+
+ private Program buildProgram() throws Exception {
+ ToyProgramBuilder builder = new ToyProgramBuilder("Test", true, this);
+
+ return builder.getProgram();
+ }
+
+ @Test
+ public void testSetGetClipBoard() throws Exception {
+
+ setClipBoardContents("foo");
+
+ Clipboard systemClipboard = GClipboard.getSystemClipboard();
+ Transferable contents = systemClipboard.getContents(this);
+ assertEquals("foo", contents.getTransferData(DataFlavor.stringFlavor));
+
+ }
+
+ @Test
+ public void testNoneValid() throws Exception {
+
+ setClipBoardContents("Hex dump of section '.text':\n" +
+ " NOTE: This section has relocations against it, but these have NOT been applied to this dump.\n");
+
+ ScriptTaskListener listener = env.runScript(script);
+
+ waitForScriptCompletion(listener, 20000);
+
+ // test that memory blocks created
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(0,blocks.length);
+ }
+
+ @Test
+ public void testNoText() throws Exception {
+
+ // clear clipboard
+ Clipboard systemClipboard = GClipboard.getSystemClipboard();
+ systemClipboard.setContents(new Transferable() {
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return flavor.equals(DataFlavor.stringFlavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ DataFlavor[] df = new DataFlavor[] { DataFlavor.stringFlavor };
+ return df;
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ return null;
+ }
+ }, new ClipboardOwner() {
+
+ @Override
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ });
+
+ ScriptTaskListener listener = env.runScript(script);
+
+ waitForScriptCompletion(listener, 20000);
+
+ // test that memory blocks created
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(0,blocks.length);
+ }
+
+ @Test
+ public void testPasteListing() throws Exception {
+
+ setClipBoardContents(
+ " 00010004 f0 07 a0 40 060 lduw [fp+local_res40],i0\n" +
+ " 00010008 81 c7 e0 0c 060 jmpl i7+0xc\n" +
+ " 0001000c 81 e8 00 00 _restore\n" +
+ " LAB_00010024 XREF[1]: 00010018 \n" +
+ " 00010024 f0 07 bf f8 lduw [fp+-0x8],i0\n" +
+ " 00010028 40 00 03 f6 call EXTERNAL:::r undefined r()\n" +
+ " 0001002c f0 27 bf _stw i0,[fp+-0xc]\n" +
+ "");
+
+ ScriptTaskListener listener = env.runScript(script);
+
+ waitForScriptCompletion(listener, 20000);
+
+ // test that memory blocks created
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(2,blocks.length);
+ MemoryBlock block = blocks[0];
+
+ assertEquals(block.getStart().getOffset(), 0x10004L);
+ assertEquals(block.getEnd().getOffset(), 0x1000fL);
+ Address addr = block.getStart();
+ assertEquals(block.getByte(addr),(byte)0xf0);
+ assertEquals(block.getByte(addr.getAddress("0x1000c")),(byte)0x81);
+ assertEquals(block.getByte(addr.getAddress("0x1000f")),(byte)0x00);
+
+ block = blocks[1];
+ assertEquals(block.getStart().getOffset(), 0x10024L);
+ assertEquals(block.getEnd().getOffset(), 0x1002eL);
+ addr = block.getStart();
+ assertEquals(block.getByte(addr.getAddress("0x10024")),(byte)0xf0);
+ assertEquals(block.getByte(addr.getAddress("0x1002a")),(byte)0x03);
+ assertEquals(block.getByte(addr.getAddress("0x1002e")),(byte)0xbf);
+
+ }
+
+ @Test
+ public void testPasteListingMultiLine() throws Exception {
+
+ setClipBoardContents(
+ " //\n" +
+ " **************************************************************\n" +
+ " * FUNCTION *\n" +
+ " **************************************************************\n" +
+ " F __stdcall f(F * __return_storage_ptr__)\n" +
+ " F o0:4 (ptr) \n" +
+ " F * Stack[0x40]: __return_storage_ptr__ XREF[1]: 00010004(R) \n" +
+ " undefined4 Stack[0x40]:4 local_res40 XREF[1]: 00010004(R) \n" +
+ " f XREF[3]: Entry Point(*), 00010018(c), \n" +
+ " _elfSectionHeaders::0000005c(*) \n" +
+ " 00010000 9d e3 0 save sp,-0x60,sp\n" +
+ " bf a0\n" +
+ " 00010004 f0 07 060 lduw [fp+local_res40],i0\n" +
+ " a0 40\n" +
+ " 00010008 81 c7 060 jmpl i7+0xc\n" +
+ " e0 0c\n" +
+ " 0001000c 81 e8 _restore\n" +
+ " 00 00\n" +
+ " 00010014 b0 07 add fp,-0x8,i0\n" +
+ " bf f8\n" +
+ " ");
+
+ ScriptTaskListener listener = env.runScript(script);
+
+ waitForScriptCompletion(listener, 20000);
+
+ // test that memory blocks created
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(2,blocks.length);
+ MemoryBlock block = blocks[0];
+
+ assertEquals(block.getStart().getOffset(), 0x10000L);
+ assertEquals(block.getEnd().getOffset(), 0x1000fL);
+ Address addr = block.getStart();
+ assertEquals(block.getByte(addr),(byte)0x9d);
+ assertEquals(block.getByte(addr.getAddress("0x1000c")),(byte)0x81);
+ assertEquals(block.getByte(addr.getAddress("0x1000f")),(byte)0x00);
+
+ block = blocks[1];
+ assertEquals(block.getStart().getOffset(), 0x10014L);
+ assertEquals(block.getEnd().getOffset(), 0x10017L);
+ addr = block.getStart();
+ assertEquals(block.getByte(addr),(byte)0xb0);
+ assertEquals(block.getByte(addr.getAddress("0x10016")),(byte)0xbf);
+ assertEquals(block.getByte(addr.getAddress("0x10017")),(byte)0xf8);
+
+ }
+
+ @Test
+ public void testPasteHexDump1Block() throws Exception {
+
+ setClipBoardContents("Hex dump of section '.text':\n" +
+ " NOTE: This section has relocations against it, but these have NOT been applied to this dump.\n" +
+ " 0x00000000 80b487b0 00aff860 c7e90023 3b683b61 .......`...#;h;a\n" +
+ " 0x00000010 b7f92030 7b61fb68 1a4607f1 100393e8 .. 0{a.h.F......\n" +
+ " 0x00000020 030082e8 0300f868 1c37bd46 5df8047b .......h.7.F]..{\n" +
+ " 0x00000030 704780b4 83b000af 87ed000b 4ff0ff33 pG..........O..3\n" +
+ " 0x00000040 18460c37 bd465df8 047b7047 80b485b0 .F.7.F]..{pG....");
+
+ ScriptTaskListener listener = env.runScript(script);
+
+ waitForScriptCompletion(listener, 20000);
+
+ // test that memory blocks created
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1,blocks.length);
+ MemoryBlock block = blocks[0];
+
+ assertEquals(block.getStart().getOffset(), 0x0000L);
+ assertEquals(block.getEnd().getOffset(), 0x004fL);
+ Address addr = block.getStart();
+ assertEquals(block.getByte(addr),(byte)0x80);
+ assertEquals(block.getByte(addr.getAddress("0x0024")),(byte)0x03);
+ assertEquals(block.getByte(addr.getAddress("0x004f")),(byte)0xb0);
+ }
+
+ @Test
+ public void testPasteHexDump3Blocks() throws Exception {
+
+ setClipBoardContents("Hex dump of section '.text':\n" +
+ " NOTE: This section has relocations against it, but these have NOT been applied to this dump.\n" +
+ " 0x00000000 80b487b0 00aff860 c7e90023 3b683b61 .......`...#;h;a\n" +
+ " 0x00000010 b7f92030 7b61fb68 1a4607f1 .. 0{a.h.F......\n" +
+ " 0x00000020 030082e8 0300f868 1c37bd46 5df8047b .......h.7.F]..{\n" +
+ " 0x00000030 704780b4 83b000af 87ed pG..........O..3\n" +
+ " 0x00000040 18460c37 bd465df8 047b7047 80b485b0 .F.7.F]..{pG....");
+
+ ScriptTaskListener listener = env.runScript(script);
+
+ waitForScriptCompletion(listener, 20000);
+
+ // test that memory blocks created
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(3,blocks.length);
+ MemoryBlock block = blocks[0];
+
+ assertEquals(28, block.getSize());
+ assertEquals(block.getStart().getOffset(), 0x00000L);
+
+ Address addr = block.getStart();
+ assertEquals((byte)0x80,block.getByte(addr.getAddress("0x00000")));
+ assertEquals((byte)0xb7,block.getByte(addr.getAddress("0x00010")));
+ assertEquals((byte)0xf1,block.getByte(addr.getAddress("0x0001b")));
+
+ block = blocks[1];
+
+ assertEquals(26, block.getSize());
+ assertEquals(block.getStart().getOffset(), 0x00020L);
+
+ addr = block.getStart();
+ assertEquals(block.getByte(addr.getAddress("0x00020")),(byte)0x03);
+ assertEquals(block.getByte(addr.getAddress("0x00030")),(byte)0x70);
+ assertEquals(block.getByte(addr.getAddress("0x00039")),(byte)0xed);
+
+ block = blocks[2];
+
+ assertEquals(16, block.getSize());
+ assertEquals(block.getStart().getOffset(), 0x00040L);
+
+ addr = block.getStart();
+ assertEquals(block.getByte(addr.getAddress("0x00040")),(byte)0x18);
+ assertEquals(block.getByte(addr.getAddress("0x00048")),(byte)0x04);
+ assertEquals(block.getByte(addr.getAddress("0x0004f")),(byte)0xb0);
+ }
+
+ private Clipboard setClipBoardContents(String str) {
+ // put stuff in copy buffer
+ Clipboard systemClipboard = GClipboard.getSystemClipboard();
+ systemClipboard.setContents(new Transferable() {
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return flavor.equals(DataFlavor.stringFlavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ DataFlavor[] df = new DataFlavor[] { DataFlavor.stringFlavor };
+ return df;
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ if (!flavor.equals(DataFlavor.stringFlavor)) {
+ return null;
+ }
+ return str;
+ }
+ }, new ClipboardOwner() {
+
+ @Override
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ });
+ return systemClipboard;
+ }
+}