GT-2703: fixed intel hex export issue of dropping bytes on selection

This commit is contained in:
adamopolous 2019-03-27 14:16:31 -04:00
parent 79d8f164f8
commit 3cbd8de918
5 changed files with 225 additions and 33 deletions

View File

@ -306,6 +306,12 @@
<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"
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>Force</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>
</BLOCKQUOTE>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -96,6 +96,15 @@ public class Option {
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() {
return null;
}

View File

@ -15,10 +15,15 @@
*/
package ghidra.app.util.exporter;
import java.awt.BorderLayout;
import java.awt.Component;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import docking.widgets.textfield.HintTextField;
import ghidra.app.util.*;
import ghidra.app.util.opinion.IntelHexRecord;
import ghidra.app.util.opinion.IntelHexRecordWriter;
@ -29,10 +34,22 @@ import ghidra.program.model.mem.*;
import ghidra.util.HelpLocation;
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>force</code> a record size for every line
* of output, which will only print out lines that match the max record size; 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 {
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;
/**
* Constructs a new Intel Hex exporter.
@ -41,6 +58,13 @@ public class IntelHexExporter extends Exporter {
this("Intel Hex", "hex", new HelpLocation("ExporterPlugin", "intel_hex"));
}
/**
* 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) {
super(name, extension, help);
}
@ -55,16 +79,150 @@ public class IntelHexExporter extends Exporter {
}
Program program = (Program) domainObject;
option = new Option("Address Space", program.getAddressFactory().getDefaultAddressSpace());
addressSpaceOption =
new Option("Address Space", program.getAddressFactory().getDefaultAddressSpace());
recordSizeOption = new RecordSizeOption("Record Size", Integer.class);
optionsList.add(addressSpaceOption);
optionsList.add(recordSizeOption);
optionsList.add(option);
return optionsList;
}
@Override
public void setOptions(List<Option> options) throws OptionException {
if (!options.isEmpty()) {
option = options.get(0);
addressSpaceOption = options.get(0);
recordSizeOption = (RecordSizeOption) options.get(1);
}
}
/**
* Option for exporting Intel Hex records that allows users to specify a record size for the
* output. Users may also optionally specify a <code>force</code> option that will only output
* lines that match this maximum size.
*
* @see RecordSizeComponent
*/
private class RecordSizeOption extends Option {
// Initialize the record size to 16 bytes when showing the option.
private final RecordSizeComponent comp = new RecordSizeComponent(16);
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 getForce() {
return comp.getForce();
}
}
/**
* 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>forceCb: a {@link JCheckBox} for specifying the <code>force</code> setting; this
* enforces that every line in the output matches the specified record size</li>
* </ul>
*
* Note: If the <code>force</code> option is set, any bytes that are left over after outputting
* all lines that match the record size will be dropped on the floor.
*/
private class RecordSizeComponent extends JPanel {
private HintTextField input;
private JCheckBox forceCb;
public RecordSizeComponent(int recordSize) {
setLayout(new BorderLayout());
input = new HintTextField(String.valueOf(recordSize), false, new BoundedIntegerVerifier());
forceCb = new JCheckBox("force");
input.setText(String.valueOf(recordSize));
add(input, BorderLayout.CENTER);
add(forceCb, BorderLayout.EAST);
}
public int getValue() {
String val = input.getText();
if (!input.isFieldValid()) {
return 0x10;
}
return Integer.valueOf(val);
}
public boolean getForce() {
return forceCb.isSelected();
}
}
/**
* 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();
// Strip off any leading "0x" chars if the user has put them there - the
// parseInt method can't handle them.
if (text.startsWith("0x")) {
text = text.substring(2);
}
// Now try and parse the input; first as hex, then as decimal.
Integer val;
try {
val = Integer.parseInt(text, 16);
}
catch (NumberFormatException e) {
try {
val = Integer.parseInt(text);
}
catch (NumberFormatException e1) {
return false;
}
}
return val <= 0xFF && val >= 0;
}
}
@ -84,11 +242,11 @@ public class IntelHexExporter extends Exporter {
return false;
}
if (option == null) {
if (addressSpaceOption == null || recordSizeOption == null) {
getOptions(() -> program);
}
PrintWriter writer = new PrintWriter(new FileOutputStream(file));
try (PrintWriter writer = new PrintWriter(new FileOutputStream(file))) {
Memory memory = program.getMemory();
@ -106,11 +264,9 @@ public class IntelHexExporter extends Exporter {
throw new ExporterException(e);
}
finally {
// Close the PrintWriter
//
writer.close();
option = null;
addressSpaceOption = null;
recordSizeOption = null;
}
}
return true;
@ -118,15 +274,19 @@ public class IntelHexExporter extends Exporter {
protected List<IntelHexRecord> dumpMemory(Program program, Memory memory,
AddressSetView addrSetView, TaskMonitor monitor) throws MemoryAccessException {
IntelHexRecordWriter writer = new IntelHexRecordWriter(MAX_BYTES_PER_LINE);
int size = (int) recordSizeOption.getValue();
boolean forceSize = recordSizeOption.getForce();
IntelHexRecordWriter writer = new IntelHexRecordWriter(size, forceSize);
AddressSet set = new AddressSet(addrSetView);
MemoryBlock[] blocks = memory.getBlocks();
for (int i = 0; i < blocks.length; ++i) {
if (!blocks[i].isInitialized() ||
blocks[i].getStart().getAddressSpace() != option.getValue()) {
set.delete(new AddressRangeImpl(blocks[i].getStart(), blocks[i].getEnd()));
for (MemoryBlock block : blocks) {
if (!block.isInitialized() ||
block.getStart().getAddressSpace() != addressSpaceOption.getValue()) {
set.delete(new AddressRangeImpl(block.getStart(), block.getEnd()));
}
}

View File

@ -20,21 +20,31 @@ import java.util.*;
import ghidra.program.model.address.*;
public class IntelHexRecordWriter {
private final int maxBytesPerLine;
private final boolean forceSize;
private Address startAddress = null;
private Long oldSegment = null;
private ArrayList<Byte> bytes = new ArrayList<Byte>();
private ArrayList<Byte> bytes = new ArrayList<>();
private Boolean isSegmented = null;
private ArrayList<IntelHexRecord> results = new ArrayList<IntelHexRecord>();
private ArrayList<IntelHexRecord> results = new ArrayList<>();
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 forceSize if true, only lines matching {@link #maxBytesPerLine} will be output;
* remaining bytes will be left out
*/
public IntelHexRecordWriter(int maxBytesPerLine, boolean forceSize) {
if (maxBytesPerLine > IntelHexRecord.MAX_RECORD_LENGTH) {
throw new IllegalArgumentException("maxBytesPerLine > IntelHexRecord.MAX_RECORD_LENGTH");
}
this.maxBytesPerLine = maxBytesPerLine;
this.forceSize = forceSize;
}
public void addByte(Address address, byte b) {
@ -117,6 +127,13 @@ public class IntelHexRecordWriter {
}
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 force option (false = write out everything).
if (bytes.size() > 0 && !forceSize) {
emitData();
}
if (entryPoint != null && isSegmented != null) {
final long offset = entryPoint.getOffset();
byte[] data = new byte[4];