Newer
Older
alicedbg / src / adbg / object / format / omf.d
/// OMF format.
///
/// This format, made by Intel, dates back to the 8080, and starting with the
/// 8086, Microsoft extensively used it on MS-DOS.
///
/// It was superseeded by COFF objects and libraries, and the MSCOFF format,
/// when Windows Vista was released.
///
/// Sources:
/// - Tool Interface Standards (TIS) Relocatable Object Module Format (OMF) Specification Version 1.1
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
module adbg.object.format.omf;

import adbg.object.server;
import adbg.error;
import core.stdc.stdlib : malloc, free;
import core.stdc.string : memcpy;

// NOTE: Checksums are either 0 or a modulo 256

/*enum {
	PAGE_SIZE = 16,
	BLOCK_SIZE = 512,
	MAX_RECORD_SIZE = 1024,
}*/

// Flags
enum : ubyte {
	/// Library flag: Case-senstive, if set. Applies to regular and extended dictionaries.
	OMF_LF_CS = 1,
}

// NOTE: Record type numbering
//       "An odd Record Type indicates that certain numeric fields within the record contain 32-bit values;"
//       "an even Record Type indicates that those fields contain 16-bit values."
// NOTE: Record type management
//       Record numbers can be masked by 0xFE, since the first bit is signifiant to mean the 16/32 selector.
/// OMF Record type
enum OMFRecord : ubyte {
	/// Library File Format Record
	LIBRARY = 0xF0,
	
	/// Translator Header Record
	THEADR = 0x80,
	/// Library Module Header Record
	LHEADR = 0x82,
	/// Comment Record
	COMENT = 0x88,
	/// Import Definition Record (Command Class A0, Subtype 01)
	IMPDEF = 0x88,
	/// Export Definition Record (Command Class A0, Subtype 02)
	EXPDEF = 0x88,
	/// Incremental Compilation Record (Command Class A0, Subtype 03)
	INCDEF = 0x88,
	/// Microsoft C++ (Linker) Directives Record (Command Class A0, Subtype 05)
	LNKDIR = 0x88,
	/// Library Module Name Record (Comment Class A3)
	LIBMOD = 0x88,
	/// Executable String Record (Comment Class A4)
	EXESTR = 0x88,
	/// Incremental Compilation error (Command Class A6)
	INCCER = 0x88,
	/// No Segment Padding (Comment Class A7)
	NOPAD  = 0x88,
	/// Weak Extern Record (Comment Class A8)
	WKEXT  = 0x88,
	/// Lazy Extern Record (Comment Class A9)
	LZEXT  = 0x88,
	/// Module End Record
	MODEND = 0x8A, // or 8B
	/// External Names Definition Record
	EXTDEF = 0x8C,
	/// Public Names Definition Record
	PUBDEF = 0x90, // or 91
	/// Line Numbers Record
	LINNUM = 0x94, // or 95 (0x96 is erratum)
	/// List of Names Record
	LNAMES = 0x96,
	/// Segment Definition Record
	SEGDEF = 0x98, // or 99
	/// Group Definition Record
	GRPDEF = 0x9A,
	/// Fixup Record
	FIXUPP = 0x9C, // or 9D
	/// Logical Enumerated Data Record
	LEDATA = 0xA0, // or A1
	/// Logical Iterated Data Record
	LIDATA = 0xA2, // or A3
	/// Communal Names Definition Record
	COMDEF = 0xB0,
	/// Backpatch Record
	BAKPAT = 0xB2, // or B3
	/// Local External Names Definition Record
	LEXTDEF = 0xB4, // or B5
	/// Local Public Names Definition Record
	LPUBDEF = 0xB6, // or B7
	/// Local Communual Names Definition Record
	LCOMFDEF = 0xB8,
	/// COMDAT External Names Definition Record
	CEXTDEF = 0xBC,
	/// Initialized Communual Data Record
	COMDAT = 0xC2, // or C3
	/// Symbol Line Numbers Record
	LINSYM = 0xC4, // or C5
	/// Alias Definition Record
	ALIAS = 0xC6,
	/// Named Backpatch Record
	NBKPAT = 0xC8, // or C9
	/// Local Logical Names Defintion Record
	LLNAMES = 0xCA,
	/// OMF Version Number Record
	VERNUM = 0xCC,
	/// Vendor-specific OMF Extension Record
	VENDEXT = 0xCE,
}

struct omf_lib_header { align(1):
	ubyte type;
	ushort size;	/// Size of this record, minus 3
	uint dicoff;	/// Directory offset
	ushort dicsize;	/// Directory size in blocks (512 B for DOS libs, limited to 251)
	ubyte flags;
}

struct omf_entry { align(1):
	ubyte type;
	ushort size;
	void *data;
	ubyte checksum;
}

int adbg_object_omf_load(adbg_object_t *o, ubyte first) {
	o.format = AdbgObject.omf;
	
	// NOTE: No swapping is done because I'm lazy
	
	switch (first) with (OMFRecord) {
	case LIBRARY: // Check library header
		// Legal values at >=4 and <=15, typically 0xd (13), since 13+3=16
		with (o.i.omf.header) if (size < 4 || size > 15)
			return adbg_oops(AdbgError.objectMalformed);
		
		o.i.omf.firstentry = o.i.omf.header.size + 3;
		//int pgsize = o.i.omf.header.size + 3;
		//int padding = o.i.omf.pgsize - cast(int)omf_lib_header.sizeof;
		//padding = o.i.omf.pgsize - cast(int)omf_lib_header.sizeof;
		break;
	case THEADR, LHEADR: // Highly recommended by spec
		o.i.omf.firstentry = 0;
		break;
	default:
		return adbg_oops(AdbgError.objectMalformed);
	}
	
	// Check first entry if it is sound
	omf_entry *entry = adbg_object_omf_entry(o, o.i.omf.firstentry);
	if (entry == null)
		return adbg_oops(AdbgError.objectMalformed);
	if (adbg_object_omf_verify(entry))
		return adbg_oops(AdbgError.objectMalformed);
	
	return 0;
}

omf_entry* adbg_object_omf_entry(adbg_object_t *o, int offset) {
	ubyte *type = void;
	ushort *length = void;
	ubyte *cksum = void;
	if (adbg_object_offsetl(o, cast(void**)&type, offset, ubyte.sizeof)) {
		adbg_oops(AdbgError.objectMalformed);
		return null;
	}
	if (adbg_object_offsetl(o, cast(void**)&length, offset + 1, ushort.sizeof)) {
		adbg_oops(AdbgError.objectMalformed);
		return null;
	}
	if (adbg_object_offsetl(o, cast(void**)&cksum, offset + *length, ubyte.sizeof)) {
		adbg_oops(AdbgError.objectMalformed);
		return null;
	}
	
	omf_entry* entry = cast(omf_entry*)malloc(omf_entry.sizeof + *length);
	if (entry == null) {
		adbg_oops(AdbgError.crt);
		return null;
	}
	void *entrydata = cast(void*)entry + omf_entry.sizeof;
	memcpy(entrydata, o.buffer + offset + 3, *length);
	
	entry.type = *type;
	entry.size = *length;
	entry.data = entrydata;
	entry.checksum = *cksum;
	return entry;
}

void adbg_object_omf_entry_free(adbg_object_t *o, omf_entry *entry) {
	if (entry == null)
		return;
	free(entry);
}

int adbg_object_omf_verify(omf_entry *entry) {
	if (entry == null)
		return 1;
	if (entry.data == null)
		return 1;
	if (entry.checksum == 0)
		return 0;
	return adbg_object_omf_chksum(entry.data, entry.size) == entry.checksum;
}

// negative sum (modulo 256)
private
ubyte adbg_object_omf_chksum(void* data, int length) {
	ubyte r;
	for (int i; i < length; ++i)
		r += (cast(ubyte*)data)[i];
	return r;
}
unittest {
	static immutable ubyte[] a1 = [ 0x31 ];
	assert(adbg_object_omf_chksum(cast(void*)a1.ptr, a1.length) == 0x31);
	static immutable ubyte[] a2 = [ 0x31, 0x31 ];
	assert(adbg_object_omf_chksum(cast(void*)a2.ptr, a2.length) == 0x62);
}

const(char)* adbg_object_omf_type_string(omf_entry *entry) {
	if (entry == null)
		Ldefault: return "UNKNOWN";
	switch (entry.type & 0xfe) with (OMFRecord) {
	case THEADR:	return "THEADR";
	case LHEADR:	return "LHEADR";
	case COMENT:	return "COMENT";
	//case IMPDEF:	return "IMPDEF";
	//case EXPDEF:	return "EXPDEF";
	//case INCDEF:	return "INCDEF";
	//case LNKDIR:	return "LNKDIR";
	//case LIBMOD:	return "LIBMOD";
	//case EXESTR:	return "EXESTR";
	//case INCCER:	return "INCCER";
	//case NOPAD:	return "NOPAD";
	//case WKEXT:	return "WKEXT";
	//case LZEXT:	return "LZEXT";
	case MODEND:	return "MODEND";
	case EXTDEF:	return "EXTDEF";
	case PUBDEF:	return "PUBDEF";
	case LINNUM:	return "LINNUM";
	case LNAMES:	return "LNAMES";
	case SEGDEF:	return "SEGDEF";
	case GRPDEF:	return "GRPDEF";
	case FIXUPP:	return "FIXUPP";
	case LEDATA:	return "LEDATA";
	case LIDATA:	return "LIDATA";
	case COMDEF:	return "COMDEF";
	case BAKPAT:	return "BAKPAT";
	case LEXTDEF:	return "LEXTDEF";
	case LPUBDEF:	return "LPUBDEF";
	case LCOMFDEF:	return "LCOMFDEF";
	case CEXTDEF:	return "CEXTDEF";
	case COMDAT:	return "COMDAT";
	case LINSYM:	return "LINSYM";
	case ALIAS:	return "ALIAS";
	case NBKPAT:	return "NBKPAT";
	case LLNAMES:	return "LLNAMES";
	case VERNUM:	return "VERNUM";
	case VENDEXT:	return "VENDEXT";
	default: goto Ldefault;
	}
}