FEAT: OMF-51 Library filesystem

This commit is contained in:
Peter Belm 2025-03-27 13:03:12 +00:00
parent 24f46bb22c
commit 64b6a358a0
9 changed files with 669 additions and 4 deletions

View file

@ -0,0 +1,110 @@
/* ###
* 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.app.util.bin.format.omf.omf.OmfLibraryRecord.MemberHeader;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
public class Omf51Library {
private Omf51RecordFactory factory;
private ArrayList<MemberHeader> members = new ArrayList<>();
public static class MemberHeader {
public long offset;
public long size;
public String name;
}
/**
* Creates a new {@link Omf51Library}
*
* @param reader A {@link BinaryReader} positioned at the start of the record
*/
public Omf51Library(Omf51RecordFactory factory) {
this.factory = factory;
}
/**
* Attempts to parse OMF-51 library members
*
* @throws IOException if an IO-related error occurred
* @throws OmfException if the required OMF-51 records could not be read
*/
public void parseMembers() throws IOException, OmfException {
OmfRecord record = factory.readNextRecord();
if (record == null || !(record instanceof Omf51LibraryHeaderRecord)) {
throw new OmfException("Unable to read library header record");
}
Omf51LibraryHeaderRecord libraryHeader = (Omf51LibraryHeaderRecord)record;
factory.getReader().setPointerIndex(libraryHeader.getModNamesOffset());
record = factory.readNextRecord();
if (record == null || !(record instanceof Omf51LibraryModuleNamesRecord)) {
throw new OmfException("Unable to read library module names record");
}
Omf51LibraryModuleNamesRecord modNamesRecord = (Omf51LibraryModuleNamesRecord)record;
record = factory.readNextRecord();
if (record == null || !(record instanceof Omf51LibraryModuleLocationsRecord)) {
throw new OmfException("Unable to read library module locations record");
}
Omf51LibraryModuleLocationsRecord modLocations = (Omf51LibraryModuleLocationsRecord)record;
List<Omf51LibraryModuleLocation> locations = modLocations.getLocations();
int index = 0;
Msg.info(this, "Iterating mod names");
for (OmfString moduleName : modNamesRecord.getNames()) {
int size = 0;
if (index + 1 < locations.size()) {
size = locations.get(index + 1).getOffset() - locations.get(index).getOffset();
} else {
size = libraryHeader.getModNamesOffset() - locations.get(index).getOffset();
}
MemberHeader header = new MemberHeader();
header.name = moduleName.str();
header.size = size;
header.offset = locations.get(index).getOffset();
members.add(header);
index++;
}
}
/**
* {@return the list of members}
*/
public ArrayList<MemberHeader> getMembers() {
return members;
}
}

View file

@ -0,0 +1,84 @@
/* ###
* 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.List;
import java.util.ArrayList;
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 Omf51LibraryDictionaryRecord extends OmfRecord {
private List<List<OmfString>> moduleSymbolMap = new ArrayList<>();
/**
* Creates a new {@link Omf51LibraryDictionaryRecord} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @throws IOException if an IO-related error occurred
*/
public Omf51LibraryDictionaryRecord(BinaryReader reader) throws IOException {
super(reader);
}
@Override
public void parseData() throws IOException, OmfException {
List<OmfString> symbols = new ArrayList<>();
while (dataReader.getPointerIndex() < dataEnd) {
byte len = dataReader.peekNextByte();
if (len == 0) {
dataReader.readNextByte();
moduleSymbolMap.add(symbols);
symbols = new ArrayList<>();
continue;
}
symbols.add(OmfUtils.readString(dataReader));
}
}
@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);
Integer moduleIndex = 0;
for (List<OmfString> symbols : moduleSymbolMap) {
for (OmfString symbol : symbols) {
struct.add(symbol.toDataType(), symbol.getDataTypeSize(), "symbol%d".formatted(moduleIndex), null);
}
struct.add(BYTE, "terminator%d".formatted(moduleIndex), null);
moduleIndex++;
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
/**
* {@return the symbol names partitioned by module}
*/
public List<List<OmfString>> getModuleSymbolMap() {
return moduleSymbolMap;
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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 ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.omf.*;
import ghidra.app.util.bin.format.omf.omf.OmfLibraryRecord.MemberHeader;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class Omf51LibraryHeaderRecord extends OmfRecord {
public static final int BLOCK_SIZE = 128;
private short moduleCount;
private short blockNumber;
private short byteNumber;
/**
* Creates a new {@link Omf51LibraryHeaderRecord} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @throws IOException if an IO-related error occurred
*/
public Omf51LibraryHeaderRecord(BinaryReader reader) throws IOException {
super(reader);
}
@Override
public void parseData() throws IOException, OmfException {
moduleCount = dataReader.readNextShort();
blockNumber = dataReader.readNextShort();
byteNumber = dataReader.readNextShort();
}
@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);
struct.add(WORD, "moduleCount", null);
struct.add(WORD, "blockNumber", null);
struct.add(WORD, "byteNumber", null);
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
/**
* {@return the module count}
*/
public short getModuleCount() {
return moduleCount;
}
/**
* {@return the module names block number}
*/
public short getModNamesBlockNumber() {
return blockNumber;
}
/**
* {@return the module names byte number}
*/
public short getModNamesByteNumber() {
return byteNumber;
}
/**
* {@return the module names file offset}
*/
public int getModNamesOffset() {
return (blockNumber * BLOCK_SIZE) + byteNumber;
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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.StructConverter;
import ghidra.app.util.bin.format.omf.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class Omf51LibraryModuleLocation {
public static final int BLOCK_SIZE = 128;
private int blockNumber;
private int byteNumber;
/**
* Creates a new {@link Omf51LibraryModuleLocation}
*
* @param reader A {@link BinaryReader} positioned at the start of the segment definition
* @throws IOException if an IO-related error occurred
*/
public Omf51LibraryModuleLocation(BinaryReader reader) throws IOException {
blockNumber = reader.readNextUnsignedShort();
byteNumber = reader.readNextUnsignedShort();
}
/**
* {@return the block number}
*/
public int getBlockNumber() {
return blockNumber;
}
/**
* {@return the byte number}
*/
public int getByteNumber() {
return byteNumber;
}
/**
* {@return the offset into the library}
*/
public int getOffset() {
return (blockNumber * BLOCK_SIZE) + byteNumber;
}
public static DataType toDataType() throws DuplicateNameException, IOException {
StructureDataType struct = new StructureDataType("Omf51LibraryModuleLocation", 0);
struct.add(StructConverter.WORD, "blockNumber", null);
struct.add(StructConverter.WORD, "byteNumber", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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 Omf51LibraryModuleLocationsRecord extends OmfRecord {
private List<Omf51LibraryModuleLocation> locations = new ArrayList<>();
/**
* Creates a new {@link Omf51LibraryModuleLocationsRecord} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @throws IOException if an IO-related error occurred
*/
public Omf51LibraryModuleLocationsRecord(BinaryReader reader) throws IOException {
super(reader);
}
@Override
public void parseData() throws IOException, OmfException {
while (dataReader.getPointerIndex() < dataEnd) {
locations.add(new Omf51LibraryModuleLocation(dataReader));
}
}
@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);
struct.add(new ArrayDataType(Omf51LibraryModuleLocation.toDataType(), locations.size()),
"locations", null);
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
/**
* {@return the list of module locations}
*/
public List<Omf51LibraryModuleLocation> getLocations() {
return locations;
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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 Omf51LibraryModuleNamesRecord extends OmfRecord {
private List<OmfString> names = new ArrayList<>();
/**
* Creates a new {@link Omf51LibraryModuleNamesRecord} record
*
* @param reader A {@link BinaryReader} positioned at the start of the record
* @throws IOException if an IO-related error occurred
*/
public Omf51LibraryModuleNamesRecord(BinaryReader reader) throws IOException {
super(reader);
}
@Override
public void parseData() throws IOException, OmfException {
while (dataReader.getPointerIndex() < dataEnd) {
names.add(OmfUtils.readString(dataReader));
}
}
@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 (OmfString name : names) {
struct.add(name.toDataType(), name.getDataTypeSize(), "name", null);
}
struct.add(BYTE, "checksum", null);
struct.setCategoryPath(new CategoryPath(OmfUtils.CATEGORY_PATH));
return struct;
}
/**
* {@return the list of module names}
*/
public List<OmfString> getNames() {
return names;
}
}

View file

@ -70,12 +70,16 @@ public class Omf51RecordFactory extends AbstractOmfRecordFactory {
yield new Omf51ExternalDefsRecord(reader, false); yield new Omf51ExternalDefsRecord(reader, false);
case KeilExternalDEF: case KeilExternalDEF:
yield new Omf51ExternalDefsRecord(reader, true); yield new Omf51ExternalDefsRecord(reader, true);
case LibModLocs:
yield new Omf51LibraryModuleLocationsRecord(reader);
case LibModNames:
yield new Omf51LibraryModuleNamesRecord(reader);
case LibDictionary:
yield new Omf51LibraryDictionaryRecord(reader);
case LibHeader:
yield new Omf51LibraryHeaderRecord(reader);
case ScopeDEF: case ScopeDEF:
case DebugItem: case DebugItem:
case LibModLocs:
case LibModNames:
case LibDictionary:
case LibHeader:
yield new OmfUnsupportedRecord(reader, Omf51RecordTypes.class); yield new OmfUnsupportedRecord(reader, Omf51RecordTypes.class);
default: default:
yield new OmfUnknownRecord(reader); yield new OmfUnknownRecord(reader);

View file

@ -0,0 +1,93 @@
/* ###
* 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.file.formats.omf51;
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
import java.io.IOException;
import java.util.ArrayList;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
import ghidra.app.util.bin.format.omf.OmfException;
import ghidra.app.util.bin.format.omf.omf51.*;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "omf51", description = "OMF51 Library", factory = Omf51ArchiveFileSystemFactory.class)
public class Omf51ArchiveFileSystem extends AbstractFileSystem<Omf51Library.MemberHeader> {
private ByteProvider provider;
public Omf51ArchiveFileSystem(FSRLRoot fsFSRL, ByteProvider provider) {
super(fsFSRL, FileSystemService.getInstance());
this.provider = provider;
}
public void mount(TaskMonitor monitor) throws IOException, OmfException {
Msg.debug(this, "Opening OMF51 library...");
Omf51RecordFactory factory = new Omf51RecordFactory(provider);
Omf51Library library = new Omf51Library(factory);
library.parseMembers();
ArrayList<Omf51Library.MemberHeader> members = library.getMembers();
Msg.debug(this, "Found %d members".formatted(members.size()));
for (Omf51Library.MemberHeader member : members) {
Msg.debug(this, member.name);
fsIndex.storeFile(member.name, fsIndex.getFileCount(), false, member.size, member);
}
}
@Override
public void close() throws IOException {
refManager.onClose();
if (provider != null) {
provider.close();
provider = null;
}
fsIndex.clear();
}
@Override
public boolean isClosed() {
return provider == null;
}
@Override
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) {
Omf51Library.MemberHeader member = fsIndex.getMetadata(file);
return (member != null)
? new ByteProviderWrapper(provider, member.offset, member.size,
file.getFSRL())
: null;
}
@Override
public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
FileAttributes result = new FileAttributes();
Omf51Library.MemberHeader entry = fsIndex.getMetadata(file);
if (entry != null) {
result.add(NAME_ATTR, entry.name);
result.add(SIZE_ATTR, entry.size);
}
return result;
}
}

View file

@ -0,0 +1,73 @@
/* ###
* 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.file.formats.omf51;
import java.io.IOException;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.omf.AbstractOmfRecordFactory;
import ghidra.app.util.bin.format.omf.OmfException;
import ghidra.app.util.bin.format.omf.OmfRecord;
import ghidra.app.util.bin.format.omf.omf51.Omf51LibraryHeaderRecord;
import ghidra.app.util.bin.format.omf.omf51.Omf51RecordFactory;
import ghidra.app.util.opinion.OmfLoader;
import ghidra.formats.gfilesystem.FSRLRoot;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider;
import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class Omf51ArchiveFileSystemFactory implements
GFileSystemFactoryByteProvider<Omf51ArchiveFileSystem>, GFileSystemProbeByteProvider {
@Override
public Omf51ArchiveFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider,
FileSystemService fsService, TaskMonitor monitor)
throws IOException, CancelledException {
Omf51ArchiveFileSystem fs = new Omf51ArchiveFileSystem(targetFSRL, byteProvider);
try {
fs.mount(monitor);
}
catch (OmfException e) {
throw new IOException(e);
}
return fs;
}
@Override
public boolean probe(ByteProvider byteProvider, FileSystemService fsService,
TaskMonitor monitor) throws IOException, CancelledException {
if (byteProvider.length() < OmfLoader.MIN_BYTE_LENGTH) {
return false;
}
try {
AbstractOmfRecordFactory factory = new Omf51RecordFactory(byteProvider);
OmfRecord record = factory.readNextRecord();
return (record != null && record instanceof Omf51LibraryHeaderRecord);
}
catch (OmfException e) {
return false;
}
catch (IOException e) {
return false;
}
}
}