Merge remote-tracking branch 'origin/adamopolous_GT-2703' into Ghidra_9.0.2

This commit is contained in:
ghidravore 2019-04-02 17:42:05 -04:00
commit 39563bed8d
6 changed files with 258 additions and 42 deletions

View File

@ -306,6 +306,12 @@
<LI><B>Address Space</B> - Specifies which address space to export as Intel Hex format <LI><B>Address Space</B> - Specifies which address space to export as Intel Hex format
only supports one address space. This option will be intialized to the "default" only supports one address space. This option will be intialized to the "default"
address space.</LI> address space.</LI>
<LI><B>Record Size</B> - Specifies the size (in bytes) of each record in the
output file. The default 16.</LI>
<LI><B>Align To Record Size</B> - If checked, this will ensure that <b>only</b> records matching
the record size will be output. eg: if you set the record size to 16 but there are
18 bytes selected, you will see only one line of 16 bytes in the output; the remaining
2 bytes will be dropped.</LI>
</UL> </UL>
</BLOCKQUOTE> </BLOCKQUOTE>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -96,6 +96,15 @@ public class Option {
this.listener = listener; this.listener = listener;
} }
/**
* Override if you want to provide a custom widget for selecting your
* options.
* <p>
* Important! If you override this you MUST also override the {@link #copy()}
* method so it returns a new instance of your custom editor.
*
* @return the custom editor
*/
public Component getCustomEditorComponent() { public Component getCustomEditorComponent() {
return null; return null;
} }

View File

@ -25,17 +25,17 @@ import ghidra.util.Msg;
public class Compare { public class Compare {
public static void compare(ArrayList<String> expectedList, File actualFile) throws Exception { public static void compare(ArrayList<String> expectedList, File actualFile) throws Exception {
int index = 0; int index = 0;
BufferedReader reader = new BufferedReader(new FileReader(actualFile));
boolean hasFailure = false; boolean hasFailure = false;
try { try (BufferedReader reader = new BufferedReader(new FileReader(actualFile))) {
int excess = 0; int excess = 0;
while (true) { while (true) {
String actualLine = reader.readLine(); String actualLine = reader.readLine();
if (actualLine == null) { if (actualLine == null) {
break; break;
} }
if (index >= expectedList.size()) { if (index >= expectedList.size()) {
++excess; ++excess;
continue; continue;
@ -73,8 +73,5 @@ public class Compare {
Assert.fail("One or more failures--see output for data"); Assert.fail("One or more failures--see output for data");
} }
} }
finally {
reader.close();
}
} }
} }

View File

@ -15,10 +15,15 @@
*/ */
package ghidra.app.util.exporter; package ghidra.app.util.exporter;
import java.awt.BorderLayout;
import java.awt.Component;
import java.io.*; import java.io.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.*;
import docking.widgets.textfield.HintTextField;
import ghidra.app.util.*; import ghidra.app.util.*;
import ghidra.app.util.opinion.IntelHexRecord; import ghidra.app.util.opinion.IntelHexRecord;
import ghidra.app.util.opinion.IntelHexRecordWriter; import ghidra.app.util.opinion.IntelHexRecordWriter;
@ -29,18 +34,55 @@ import ghidra.program.model.mem.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/**
* Exports the current program (or program selection) as bytes in Intel Hex format.
* <p>
* The output defaults to lines of 16-bytes but this is configurable using the
* {@link #recordSizeOption} attribute. This allows users to select any record size
* up to the max of 0xFF. Users may also choose to <code>Drop Extra Bytes</code>, which will
* cause only lines that match the max record size to be printed; any other
* bytes will be dropped. If this option is not set, every byte will be represented in the output.
*/
public class IntelHexExporter extends Exporter { public class IntelHexExporter extends Exporter {
protected final static int MAX_BYTES_PER_LINE = 0x00000010;
protected Option option;
/** Option allowing the user to select the address space */
protected Option addressSpaceOption;
/** Option allowing the user to select the number of bytes in each line of output */
protected RecordSizeOption recordSizeOption;
private static final int DEFAULT_RECORD_SIZE = 0x10;
/** /**
* Constructs a new Intel Hex exporter. * Constructs a new Intel Hex exporter. This will use a record size of 16 (the default)
* and will export ALL bytes in the program or selection (even if the total length
* is not a multiple of 16.
*/ */
public IntelHexExporter() { public IntelHexExporter() {
this("Intel Hex", "hex", new HelpLocation("ExporterPlugin", "intel_hex")); this("Intel Hex", "hex", new HelpLocation("ExporterPlugin", "intel_hex"));
} }
/**
* Constructs a new Intel Hex exporter with a custom record size.
*
* @param recordSize the record size to use when writing to the output file
* @param dropBytes if true, bytes at the end of the file that don't match the specified
* record size will be dropped
*/
public IntelHexExporter(int recordSize, boolean dropBytes) {
this("Intel Hex", "hex", new HelpLocation("ExporterPlugin", "intel_hex"));
recordSizeOption = new RecordSizeOption("Record Size", Integer.class);
recordSizeOption.setRecordSize(recordSize);
recordSizeOption.setDropBytes(dropBytes);
}
/**
* Constructor
*
* @param name the name of the exporter
* @param extension the extension to use for the output file
* @param help location of Ghidra help
*/
protected IntelHexExporter(String name, String extension, HelpLocation help) { protected IntelHexExporter(String name, String extension, HelpLocation help) {
super(name, extension, help); super(name, extension, help);
} }
@ -55,16 +97,49 @@ public class IntelHexExporter extends Exporter {
} }
Program program = (Program) domainObject; Program program = (Program) domainObject;
option = new Option("Address Space", program.getAddressFactory().getDefaultAddressSpace()); addressSpaceOption =
new Option("Address Space", program.getAddressFactory().getDefaultAddressSpace());
if (recordSizeOption == null) {
recordSizeOption = new RecordSizeOption("Record Size", Integer.class);
}
optionsList.add(addressSpaceOption);
optionsList.add(recordSizeOption);
optionsList.add(option);
return optionsList; return optionsList;
} }
@Override @Override
public void setOptions(List<Option> options) throws OptionException { public void setOptions(List<Option> options) throws OptionException {
if (!options.isEmpty()) { if (!options.isEmpty()) {
option = options.get(0); addressSpaceOption = options.get(0);
recordSizeOption = (RecordSizeOption) options.get(1);
}
}
/**
* Verifier for a {@link HintTextField} that ensures input is a numeric value between
* 0 and 0xFF.
* <p>
* Input may be specified in either decimal or hex.
*/
private class BoundedIntegerVerifier extends InputVerifier {
@Override
public boolean verify(JComponent input) {
HintTextField field = (HintTextField) input;
String text = field.getText();
int val;
try {
val = Integer.decode(text);
}
catch (NumberFormatException e) {
return false;
}
return val <= 0xFF && val >= 0;
} }
} }
@ -84,33 +159,31 @@ public class IntelHexExporter extends Exporter {
return false; return false;
} }
if (option == null) { if (addressSpaceOption == null || recordSizeOption == null) {
getOptions(() -> program); getOptions(() -> program);
} }
PrintWriter writer = new PrintWriter(new FileOutputStream(file)); try (PrintWriter writer = new PrintWriter(new FileOutputStream(file))) {
Memory memory = program.getMemory(); Memory memory = program.getMemory();
if (addrSet == null) { if (addrSet == null) {
addrSet = memory; addrSet = memory;
}
try {
List<IntelHexRecord> records = dumpMemory(program, memory, addrSet, monitor);
for (IntelHexRecord record : records) {
writer.println(record.format());
} }
}
catch (MemoryAccessException e) {
throw new ExporterException(e);
}
finally {
// Close the PrintWriter
//
writer.close();
option = null; try {
List<IntelHexRecord> records = dumpMemory(program, memory, addrSet, monitor);
for (IntelHexRecord record : records) {
writer.println(record.format());
}
}
catch (MemoryAccessException e) {
throw new ExporterException(e);
}
finally {
addressSpaceOption = null;
recordSizeOption = null;
}
} }
return true; return true;
@ -118,15 +191,19 @@ public class IntelHexExporter extends Exporter {
protected List<IntelHexRecord> dumpMemory(Program program, Memory memory, protected List<IntelHexRecord> dumpMemory(Program program, Memory memory,
AddressSetView addrSetView, TaskMonitor monitor) throws MemoryAccessException { AddressSetView addrSetView, TaskMonitor monitor) throws MemoryAccessException {
IntelHexRecordWriter writer = new IntelHexRecordWriter(MAX_BYTES_PER_LINE);
int size = (int) recordSizeOption.getValue();
boolean dropBytes = recordSizeOption.dropExtraBytes();
IntelHexRecordWriter writer = new IntelHexRecordWriter(size, dropBytes);
AddressSet set = new AddressSet(addrSetView); AddressSet set = new AddressSet(addrSetView);
MemoryBlock[] blocks = memory.getBlocks(); MemoryBlock[] blocks = memory.getBlocks();
for (int i = 0; i < blocks.length; ++i) { for (MemoryBlock block : blocks) {
if (!blocks[i].isInitialized() || if (!block.isInitialized() ||
blocks[i].getStart().getAddressSpace() != option.getValue()) { block.getStart().getAddressSpace() != addressSpaceOption.getValue()) {
set.delete(new AddressRangeImpl(blocks[i].getStart(), blocks[i].getEnd())); set.delete(new AddressRangeImpl(block.getStart(), block.getEnd()));
} }
} }
@ -148,4 +225,113 @@ public class IntelHexExporter extends Exporter {
} }
return writer.finish(entryPoint); return writer.finish(entryPoint);
} }
/**
* Option for exporting Intel Hex records that allows users to specify a record size for the
* output. Users may also optionally select the <code>Drop Extra Bytes</code> option that
* will cause only those records that match the maximum size to be output to the file.
*
* @see RecordSizeComponent
*/
private class RecordSizeOption extends Option {
private final RecordSizeComponent comp = new RecordSizeComponent(DEFAULT_RECORD_SIZE);
public RecordSizeOption(String name, Class<?> valueClass) {
super(name, valueClass);
}
public RecordSizeOption(String name, Class<?> valueClass, Object value, String arg,
String group) {
super(name, valueClass, value, arg, group);
}
@Override
public Component getCustomEditorComponent() {
return comp;
}
@Override
public Option copy() {
return new RecordSizeOption(getName(), getValueClass(), getValue(), getArg(),
getGroup());
}
@Override
public Object getValue() {
return comp.getValue();
}
@Override
public Class<?> getValueClass() {
return Integer.class;
}
public boolean dropExtraBytes() {
return comp.dropExtraBytes();
}
public void setRecordSize(int recordSize) {
comp.setRecordSize(recordSize);
}
public void setDropBytes(boolean dropBytes) {
comp.setDropBytes(dropBytes);
}
}
/**
* Component that displays two widgets for setting export options:
*
* <ul>
* <li><code>input</code>: a {@link HintTextField} for entering numeric digits; these
* represent the record size for each line of output</li>
* <li>dropCb: a {@link JCheckBox} for specifying a setting that enforces that every line in
* the output matches the specified record size</li>
* </ul>
*
* Note: If the <code>Drop Extra Bytes</code> option is set, any bytes that are left over
* after outputting all lines that match the record size will be omitted from the output.
*/
private class RecordSizeComponent extends JPanel {
private HintTextField input;
private JCheckBox dropCb;
public RecordSizeComponent(int recordSize) {
setLayout(new BorderLayout());
input = new HintTextField(Integer.toString(recordSize), false, new BoundedIntegerVerifier());
dropCb = new JCheckBox("Align To Record Size");
input.setText(Integer.toString(recordSize));
add(input, BorderLayout.CENTER);
add(dropCb, BorderLayout.EAST);
}
public int getValue() {
String val = input.getText();
if (!input.isFieldValid()) {
// If the user clears the input field, revert to the default
// record size (16).
return DEFAULT_RECORD_SIZE;
}
return Integer.valueOf(val);
}
public boolean dropExtraBytes() {
return dropCb.isSelected();
}
public void setRecordSize(int recordSize) {
input.setText(Integer.toString(recordSize));
}
public void setDropBytes(boolean dropBytes) {
dropCb.setSelected(dropBytes);
}
}
} }

View File

@ -20,21 +20,31 @@ import java.util.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
public class IntelHexRecordWriter { public class IntelHexRecordWriter {
private final int maxBytesPerLine; private final int maxBytesPerLine;
private final boolean dropExtraBytes;
private Address startAddress = null; private Address startAddress = null;
private Long oldSegment = null; private Long oldSegment = null;
private ArrayList<Byte> bytes = new ArrayList<Byte>(); private ArrayList<Byte> bytes = new ArrayList<>();
private Boolean isSegmented = null; private Boolean isSegmented = null;
private ArrayList<IntelHexRecord> results = new ArrayList<IntelHexRecord>(); private ArrayList<IntelHexRecord> results = new ArrayList<>();
private boolean done = false; private boolean done = false;
public IntelHexRecordWriter(int maxBytesPerLine) { /**
* Constructor
*
* @param maxBytesPerLine the maximum number of bytes to write per line in the hex output
* @param dropExtraBytes if true, only lines matching {@link #maxBytesPerLine} will be output;
* remaining bytes will be left out
*/
public IntelHexRecordWriter(int maxBytesPerLine, boolean dropExtraBytes) {
if (maxBytesPerLine > IntelHexRecord.MAX_RECORD_LENGTH) { if (maxBytesPerLine > IntelHexRecord.MAX_RECORD_LENGTH) {
throw new IllegalArgumentException("maxBytesPerLine > IntelHexRecord.MAX_RECORD_LENGTH"); throw new IllegalArgumentException("maxBytesPerLine > IntelHexRecord.MAX_RECORD_LENGTH");
} }
this.maxBytesPerLine = maxBytesPerLine; this.maxBytesPerLine = maxBytesPerLine;
this.dropExtraBytes = dropExtraBytes;
} }
public void addByte(Address address, byte b) { public void addByte(Address address, byte b) {
@ -117,6 +127,14 @@ public class IntelHexRecordWriter {
} }
public List<IntelHexRecord> finish(Address entryPoint) { public List<IntelHexRecord> finish(Address entryPoint) {
// Before finalizing things, write out any remaining bytes that haven't yet been written, if
// the user has specified to do so via the drop extra bytes option (false =
// write out everything).
if (bytes.size() > 0 && !dropExtraBytes) {
emitData();
}
if (entryPoint != null && isSegmented != null) { if (entryPoint != null && isSegmented != null) {
final long offset = entryPoint.getOffset(); final long offset = entryPoint.getOffset();
byte[] data = new byte[4]; byte[] data = new byte[4];