GP-4928 Added support for Hexdump and multi-line bytes field to PasteCopyiedListingBytesScript. Also added JUNIT tests.

This commit is contained in:
emteere 2024-09-13 16:45:31 -04:00
parent e5aa0e8e5b
commit 35a4455826
2 changed files with 448 additions and 26 deletions

View file

@ -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<Address, byte[]> bytesToAdd = parseListingStringToByte(ClipboardText);
Map<Address, byte[]> 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<Address, byte[]> bytesToAdd) {
// Map is assumed to be a sorted Map
Set<Entry<Address, byte[]>> entrySet = bytesToAdd.entrySet();
Iterator<Entry<Address, byte[]>> iterator = entrySet.iterator();
Map.Entry<Address, byte[]> entryA = iterator.next();
while (entryA != null && iterator.hasNext()) {
Map.Entry<Address, byte[]> 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<Address, byte[]> bytesToAdd, Memory memory)
throws CancelledException, Exception {
AddressSet neededMem = getNeededAddressSet(bytesToAdd);
@ -151,44 +200,85 @@ public class PasteCopiedListingBytesScript extends GhidraScript {
private Map<Address, byte[]> parseListingStringToByte(String ClipboardText)
throws CancelledException {
// TreeMap so the entries will be sorted by Address
Map<Address, byte[]> newMap = new TreeMap<Address, byte[]>();
String[] bufferLines = ClipboardText.split("\n");
Map<Address, byte[]> newMap = new HashMap<Address, byte[]>();
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<String> bytesFound = new ArrayList<String>();
List<String> bytesStringsList = new ArrayList<String>();
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<String> 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;
}
}