GP-0: OMF-51 improvements

This commit is contained in:
Ryan Kurtz 2025-02-21 13:52:25 -05:00
parent 26d3c933e7
commit 22a4de14ea
8 changed files with 525 additions and 21 deletions

View file

@ -4,9 +4,9 @@
* 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.
@ -20,6 +20,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.data.*;
@ -132,4 +133,18 @@ public class OmfUtils {
return records;
}
/**
* Returns a {@link Stream} of {@link OmfRecord records} that match the given class type
*
* @param <T> The class type
* @param records The {@link List} of all {@link OmfRecord records}
* @param classType The class type to match on
* @return A {@link Stream} of matching (@link OmfRecord records}
*/
public static <T> Stream<T> filterRecords(List<OmfRecord> records, Class<T> classType) {
return records.stream()
.filter(classType::isInstance)
.map(classType::cast);
}
}

View file

@ -24,31 +24,38 @@ import ghidra.util.exception.DuplicateNameException;
public class Omf51Content extends OmfRecord {
private byte segId;
private int segId;
private int offset;
private byte[] dataBytes;
private long dataIndex;
private int dataSize;
boolean largeSegmentId;
/**
* Creates a new {@link Omf51Content} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @param largeSegmentId True if the segment ID is 2 bytes; false if 1 byte
* @throws IOException if an IO-related error occurred
*/
public Omf51Content(BinaryReader reader) throws IOException {
public Omf51Content(BinaryReader reader, boolean largeSegmentId) throws IOException {
super(reader);
this.largeSegmentId = largeSegmentId;
}
@Override
public void parseData() throws IOException, OmfException {
segId = dataReader.readNextByte();
segId =
largeSegmentId ? dataReader.readNextUnsignedShort() : dataReader.readNextUnsignedByte();
offset = dataReader.readNextUnsignedShort();
dataBytes = dataReader.readNextByteArray((int) (dataEnd - dataReader.getPointerIndex()));
dataIndex = dataReader.getPointerIndex();
dataSize = (int) (dataEnd - dataIndex);
}
/**
* {@return the segment ID}
*/
public byte getSegId() {
public int getSegId() {
return segId;
}
@ -60,10 +67,17 @@ public class Omf51Content extends OmfRecord {
}
/**
* {@return the data}
* {@return the data size in bytes}
*/
public byte[] getDataBytes() {
return dataBytes;
public int getDataSize() {
return dataSize;
}
/**
* {@return the start of the data in the reader}
*/
public long getDataIndex() {
return dataIndex;
}
@Override
@ -71,10 +85,10 @@ public class Omf51Content extends OmfRecord {
StructureDataType struct = new StructureDataType(Omf51RecordTypes.getName(recordType), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
struct.add(BYTE, "SEG ID", null);
struct.add(largeSegmentId ? WORD : BYTE, "SEG ID", null);
struct.add(WORD, "offset", null);
if (dataBytes.length > 0) {
struct.add(new ArrayDataType(BYTE, dataBytes.length, 1), "data", null);
if (dataSize > 0) {
struct.add(new ArrayDataType(BYTE, dataSize, 1), "data", null);
}
struct.add(BYTE, "checksum", null);

View file

@ -0,0 +1,85 @@
/* ###
* 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.util.bin.format.omf.omf51;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.omf.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class Omf51FixupRecord extends OmfRecord {
/**
* OMF-51 fixup metadata
*
* @param refLoc The reference location
* @param refType The reference type
* @param operand the fixup operand
*/
public static record Omf51Fixup(int refLoc, byte refType, int operand) {}
private List<Omf51Fixup> fixups = new ArrayList<>();
/**
* Creates a new {@link Omf51FixupRecord} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @throws IOException if an IO-related error occurred
*/
public Omf51FixupRecord(BinaryReader reader) throws IOException {
super(reader);
}
@Override
public void parseData() throws IOException, OmfException {
while (dataReader.getPointerIndex() < dataEnd) {
int refLoc = dataReader.readNextUnsignedByte();
byte refType = dataReader.readNextByte();
int operand = dataReader.readNextUnsignedShort();
fixups.add(new Omf51Fixup(refLoc, refType, operand));
}
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(Omf51RecordTypes.getName(recordType), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
StructureDataType fixupStruct = new StructureDataType("Omf51Fixup", 0);
fixupStruct.add(BYTE, "ref_loc", null);
fixupStruct.add(BYTE, "ref_type", null);
fixupStruct.add(WORD, "operand", null);
struct.add(new ArrayDataType(fixupStruct, fixups.size(), fixupStruct.getLength()), "fixup",
null);
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
/**
* Gets a {@link List} of fixups
*
* @return A {@link List} of fixups
*/
public List<Omf51Fixup> getFixups() {
return fixups;
}
}

View file

@ -23,8 +23,8 @@ import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.omf.*;
import ghidra.app.util.bin.format.omf.omf166.Omf166RecordTypes;
import ghidra.app.util.bin.format.omf.omf166.Omf166DepList;
import ghidra.app.util.bin.format.omf.omf166.Omf166RecordTypes;
/**
* A class for reading/creating OMF-51 records
@ -51,9 +51,15 @@ public class Omf51RecordFactory extends AbstractOmfRecordFactory {
case Omf166RecordTypes.DEPLST:
yield new Omf166DepList(reader);
case Content:
yield new Omf51Content(reader);
case Fixup:
case KeilContent:
yield new Omf51Content(reader, true);
case SegmentDEF:
yield new Omf51SegmentDefs(reader, false);
case KeilSegmentDEF:
yield new Omf51SegmentDefs(reader, true);
case KeilFixup:
yield new Omf51FixupRecord(reader);
case Fixup:
case ScopeDEF:
case DebugItem:
case PublicDEF:

View file

@ -40,6 +40,8 @@ public class Omf51RecordTypes {
// Record types with names ending in "Keil", which are produced by ARM Keil's
// 8051 tooling, are only slight variants of the similarly-named record types in the Intel spec.
public final static int KeilContent = Content + 1;
public final static int KeilFixup = Fixup + 1;
public final static int KeilSegmentDEF = SegmentDEF + 1;
public final static int KeilScopeDEF = ScopeDEF + 1;
public final static int KeilDebugItemOBJ = 0x22; // Keil debug items, in linker output format

View file

@ -0,0 +1,137 @@
/* ###
* 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.util.bin.format.omf.omf51;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.omf.OmfString;
import ghidra.app.util.bin.format.omf.OmfUtils;
public class Omf51Segment {
// Segment Types
public static final int CODE = 0;
public static final int XDATA = 1;
public static final int DATA = 2;
public static final int IDATA = 3;
public static final int BIT = 4;
// Relocation Types
public static final int ABS = 0;
public static final int UNIT = 1;
public static final int BITADDRESSABLE = 2;
public static final int INPAGE = 3;
public static final int INBLOCK = 4;
public static final int PAGE = 5;
private int id;
private byte info;
private byte relType;
private int base;
private int size;
private OmfString name;
/**
* Creates a new {@link Omf51Segment}
*
* @param reader A {@link BinaryReader} positioned at the start of the segment definition
* @param largeSegmentId True if the segment ID is 2 bytes; false if 1 byte
* @throws IOException if an IO-related error occurred
*/
public Omf51Segment(BinaryReader reader, boolean largeSegmentId) throws IOException {
id = largeSegmentId ? reader.readNextUnsignedShort() : reader.readNextUnsignedByte();
info = reader.readNextByte();
relType = reader.readNextByte();
reader.readNextByte(); // unused
base = reader.readNextUnsignedShort();
size = reader.readNextUnsignedShort();
name = OmfUtils.readString(reader);
// Size of 0 is used to represent 0x10000
if (size == 0) {
size = 0x10000;
}
// Size is ignored if empty bit is set, but force it to be 0 for consistency
if ((info & 0x8) != 0) {
size = 0;
}
}
/**
* {@return the segment id}
*/
public int id() {
return id;
}
/**
* {@return the segment info}
*/
public byte info() {
return info;
}
/**
* {@return the segment relocation type}
*/
public byte relType() {
return relType;
}
/**
* {@return the segment base address}
*/
public int base() {
return base;
}
/**
* {@return the segment size}
*/
public int size() {
return size;
}
/**
* {@return the segment name}
*/
public OmfString name() {
return name;
}
/**
* {@return the segment type (CODE, XDATA, etc)}
*/
public int getType() {
return info & 7;
}
/**
* {@return whether or not this segment is code}
*/
public boolean isCode() {
return getType() == CODE;
}
/**
* {@return whether or not this segment is absolute}
*/
public boolean isAbsolute() {
return id == 0;
}
}

View file

@ -0,0 +1,77 @@
/* ###
* 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.util.bin.format.omf.omf51;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.omf.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class Omf51SegmentDefs extends OmfRecord {
private boolean largeSegmentId;
private List<Omf51Segment> segments = new ArrayList<>();
/**
* Creates a new {@link Omf51SegmentDefs} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @param largeSegmentId True if the segment ID is 2 bytes; false if 1 byte
* @throws IOException if an IO-related error occurred
*/
public Omf51SegmentDefs(BinaryReader reader, boolean largeSegmentId) throws IOException {
super(reader);
this.largeSegmentId = largeSegmentId;
}
@Override
public void parseData() throws IOException, OmfException {
while (dataReader.getPointerIndex() < dataEnd) {
segments.add(new Omf51Segment(dataReader, largeSegmentId));
}
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType(Omf51RecordTypes.getName(recordType), 0);
struct.add(BYTE, "type", null);
struct.add(WORD, "length", null);
for (Omf51Segment segment : segments) {
struct.add(largeSegmentId ? WORD : BYTE, "id", null);
struct.add(BYTE, "info", null);
struct.add(BYTE, "rel type", null);
struct.add(BYTE, "unused", null);
struct.add(WORD, "base", null);
struct.add(WORD, "size", null);
struct.add(segment.name().toDataType(), segment.name().getDataTypeSize(), "name", null);
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
/**
* {@return the list of segments}
*/
public List<Omf51Segment> getSegments() {
return segments;
}
}

View file

@ -17,21 +17,23 @@ package ghidra.app.util.opinion;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.StructConverter;
import ghidra.app.util.bin.format.omf.*;
import ghidra.app.util.bin.format.omf.omf51.Omf51RecordFactory;
import ghidra.app.util.bin.format.omf.omf51.*;
import ghidra.app.util.bin.format.omf.omf51.Omf51FixupRecord.Omf51Fixup;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataUtilities;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.reloc.Relocation.Status;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -79,13 +81,114 @@ public class Omf51Loader extends AbstractProgramWrapperLoader {
AbstractOmfRecordFactory factory = new Omf51RecordFactory(provider);
try {
List<OmfRecord> records = OmfUtils.readRecords(factory);
Map<Integer, Address> segmentToAddr =
processMemoryBlocks(program, fileBytes, records, log, monitor);
performFixups(program, fileBytes, records, segmentToAddr, log, monitor);
markupRecords(program, fileBytes, records, log, monitor);
}
catch (OmfException e) {
catch (Exception e) {
throw new IOException(e);
}
}
private Map<Integer, Address> processMemoryBlocks(Program program, FileBytes fileBytes,
List<OmfRecord> records, MessageLog log, TaskMonitor monitor) throws Exception {
// Gather all segments for processing, putting the absolute segments (id == 0) first since
// they are not flexible about where they get placed
List<Omf51Segment> segments = OmfUtils.filterRecords(records, Omf51SegmentDefs.class)
.map(Omf51SegmentDefs::getSegments)
.flatMap(List::stream)
.sorted((a, b) -> Integer.compare(a.id(), b.id())) // absolute (id=0) comes first
.toList();
// Group all of a segment's content records together
Map<Integer, List<Omf51Content>> contentMap =
OmfUtils.filterRecords(records, Omf51Content.class)
.collect(Collectors.groupingBy(Omf51Content::getSegId));
// Create some data structures that will aid in segment relocation:
// - A set of addresses currently in use, so we can find holes for new segments in the
// address space
// - A map to keep track of segment's size, since segments with different ID's but the
// same name and type must be adjacent
// - A map to keep track of where same-named segments currently ends, so the next
// same-named segment can easily know where to go
// NOTE: The key for these maps needs to include both the segment name and the type
AddressSet usedAddresses = new AddressSet();
Map<String, Integer> segmentSizes = new HashMap<>();
Map<String, Address> segmentEnds = new HashMap<>();
for (Omf51Segment segment : segments) {
segmentSizes.compute(key(segment),
(k, v) -> (v == null ? segment.size() : v + segment.size()));
}
// We will be returning a map of segment ID's to starting address, for use when we later
// perform fixups within the content
Map<Integer, Address> segmentToAddr = new HashMap<>();
for (Omf51Segment segment : segments) {
List<Omf51Content> segmentContent = contentMap.get(segment.id());
String blockName = segment.isAbsolute() ? "<ABSOLUTE>" : segment.name().str();
if (blockName.isBlank()) {
blockName = "<NONAME>";
}
AddressSpace space = getAddressSpace(program, segment);
Address segmentAddr;
if (segmentContent != null) {
segmentAddr = findAddr(segment, segmentSizes, segmentEnds, space, usedAddresses);
for (Omf51Content content : segmentContent) {
Address contentAddr =
segment.isAbsolute() ? space.getAddress(content.getOffset())
: segmentAddr.add(content.getOffset());
try {
MemoryBlockUtils.createInitializedBlock(program, false, blockName,
contentAddr, fileBytes, content.getDataIndex(), content.getDataSize(),
"", space.getName(), true, !segment.isCode(), segment.isCode(), log);
}
catch (Exception e) {
log.appendMsg(e.getMessage());
}
}
}
else {
segmentAddr = findAddr(segment, segmentSizes, segmentEnds, space, usedAddresses);
MemoryBlockUtils.createUninitializedBlock(program, false, blockName, segmentAddr,
segment.size(), "", space.getName(), true, true, false, log);
}
if (segment.isCode()) {
AbstractProgramLoader.markAsFunction(program, blockName, segmentAddr);
}
segmentToAddr.put(segment.id(), segmentAddr);
}
return segmentToAddr;
}
private void performFixups(Program program, FileBytes fileBytes, List<OmfRecord> records,
Map<Integer, Address> segmentToAddr, MessageLog log, TaskMonitor monitor)
throws Exception {
OmfRecord previous = null;
for (OmfRecord record : records) {
if (record instanceof Omf51FixupRecord fixupRec) {
if (!(previous instanceof Omf51Content content)) {
throw new Exception("Record prior to fixup is not content!");
}
Address segmentAddr = segmentToAddr.get(content.getSegId());
if (segmentAddr == null) {
throw new Exception("Failed to get lookup segment ID 0x%x for content fixup!"
.formatted(content.getSegId()));
}
for (Omf51Fixup fixup : fixupRec.getFixups()) {
Address fixupAddr = segmentAddr.add(fixup.refLoc());
program.getRelocationTable()
.add(fixupAddr, Status.UNSUPPORTED, fixup.refType(),
new long[] { fixup.operand() }, 0, null);
}
}
previous = record;
}
}
private void markupRecords(Program program, FileBytes fileBytes, List<OmfRecord> records,
MessageLog log, TaskMonitor monitor) {
monitor.setMessage("Marking up records...");
@ -114,6 +217,71 @@ public class Omf51Loader extends AbstractProgramWrapperLoader {
}
}
private Address findAddr(Omf51Segment segment, Map<String, Integer> segmentSizes,
Map<String, Address> segmentEnds, AddressSpace space, AddressSet usedAddresses)
throws Exception {
return switch (segment.relType()) {
case Omf51Segment.ABS: {
if (segment.id() != 0) {
throw new Exception("Absolute segment does not have ID 0!");
}
Address start = space.getAddress(segment.base());
Address end = start.add(segment.size());
if (usedAddresses.intersects(start, end)) {
throw new Exception("Absolute segment overlaps with existing segment!");
}
usedAddresses.add(start, end);
yield start;
}
case Omf51Segment.UNIT: {
Address lastEnd = segmentEnds.get(key(segment));
if (lastEnd != null) {
Address start = lastEnd.add(1);
Address end = start.add(segment.size() - 1);
segmentEnds.put(key(segment), end);
yield start;
}
Address start = space.getMinAddress();
Address end = start.add(segment.size() - 1);
int requiredSize = segmentSizes.get(key(segment));
AddressSet intersection =
usedAddresses.intersectRange(start, start.add(requiredSize - 1));
while (!intersection.isEmpty()) {
start = intersection.getMaxAddress().add(1);
end = start.add(segment.size() - 1);
intersection = usedAddresses.intersectRange(start, start.add(requiredSize - 1));
}
usedAddresses.add(start, start.add(requiredSize - 1));
segmentEnds.put(key(segment), end);
yield start;
}
case Omf51Segment.BITADDRESSABLE:
case Omf51Segment.INPAGE:
case Omf51Segment.INBLOCK:
case Omf51Segment.PAGE:
default:
throw new Exception(
"Skipping segment '%s'. Relocation type 0x%x is not yet supported"
.formatted(segment.name(), segment.relType()));
};
}
private AddressSpace getAddressSpace(Program program, Omf51Segment segment) throws Exception {
return program.getAddressFactory().getAddressSpace(switch (segment.getType()) {
case Omf51Segment.CODE -> "CODE";
case Omf51Segment.XDATA -> "EXTMEM";
case Omf51Segment.DATA -> "INTMEM";
case Omf51Segment.IDATA -> "INTMEM";
case Omf51Segment.BIT -> "BITS";
default -> throw new Exception(
"Unsupported address space: 0x%x".formatted(segment.getType()));
});
}
private String key(Omf51Segment segment) {
return segment.name().str() + "_" + segment.getType();
}
@Override
public String getName() {
return OMF51_NAME;