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 <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>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> </UL>
</BLOCKQUOTE> </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; 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

@ -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,10 +34,22 @@ 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>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 { 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. * Constructs a new Intel Hex exporter.
@ -41,6 +58,13 @@ public class IntelHexExporter extends Exporter {
this("Intel Hex", "hex", new HelpLocation("ExporterPlugin", "intel_hex")); 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) { protected IntelHexExporter(String name, String extension, HelpLocation help) {
super(name, extension, help); super(name, extension, help);
} }
@ -55,16 +79,150 @@ 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());
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);
}
}
/**
* 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,33 +242,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 +274,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 forceSize = recordSizeOption.getForce();
IntelHexRecordWriter writer = new IntelHexRecordWriter(size, forceSize);
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()));
} }
} }

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 forceSize;
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 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) { 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.forceSize = forceSize;
} }
public void addByte(Address address, byte b) { public void addByte(Address address, byte b) {
@ -117,6 +127,13 @@ 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 force option (false = write out everything).
if (bytes.size() > 0 && !forceSize) {
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];