From 9f2bb1b1b30a1f63e123fd2f63353b38501a2af5 Mon Sep 17 00:00:00 2001
From: dd86k
Date: Sat, 4 May 2024 16:35:19 -0400
Subject: [PATCH] Add support for OMF libraries and objects
---
dumper/dumper.d | 49 ++++---
dumper/format/omf.d | 79 ++++++++++
dumper/format/package.d | 1 +
src/adbg/object/format/omf.d | 273 +++++++++++++++++++++++++++++++++++
src/adbg/object/formats.d | 1 +
src/adbg/object/server.d | 34 ++++-
6 files changed, 413 insertions(+), 24 deletions(-)
create mode 100644 dumper/format/omf.d
create mode 100644 src/adbg/object/format/omf.d
diff --git a/dumper/dumper.d b/dumper/dumper.d
index 3b7fa5c2..b8e61206 100644
--- a/dumper/dumper.d
+++ b/dumper/dumper.d
@@ -20,6 +20,8 @@ import common.error;
import common.cli : opt_machine, opt_syntax;
import common.utils;
+//TODO: print_error(string)
+
extern (C):
__gshared:
@@ -144,6 +146,7 @@ int dump(const(char)* path) {
case archive: return dump_archive(o);
case mdmp: return dump_minidump(o);
case dmp: return dump_dmp(o);
+ case omf: return dump_omf(o);
case coff: return dump_coff(o);
case mscoff: return dump_mscoff(o);
case unknown: assert(0, "Unknown object type"); // Raw/unknown
@@ -151,7 +154,7 @@ int dump(const(char)* path) {
}
// Otherwise, make a basic summary
- printf("%s: %s (%s), %s\n", path,
+ printf("%s: %s (%s), %s", path,
adbg_object_format_name(o), adbg_object_format_shortname(o),
adbg_object_format_kind_string(o));
@@ -286,41 +289,43 @@ void print_stringl(const(char)* name, const(char)* val, int len) {
}
//TODO: print_stringf
-void print_flags16(const(char) *section, ushort flags, ...) {
- printf("%*s: 0x%04x\t(", __field_padding, section, flags);
-
- va_list args = void;
- va_start(args, flags);
+
+private
+void print_flagsv(int flags, va_list list) {
ushort count;
-L_START:
- const(char) *name = va_arg!(const(char)*)(args);
+Lfetch:
+ const(char) *name = va_arg!(const(char)*)(list);
if (name == null) {
puts(")");
return;
}
- if ((flags & va_arg!int(args)) == 0) goto L_START; // condition
+ if ((flags & va_arg!int(list)) == 0) goto Lfetch; // condition
if (count++) putchar(',');
printf("%s", name);
- goto L_START;
+ goto Lfetch;
+}
+
+void print_flags8(const(char) *section, ubyte flags, ...) {
+ printf("%*s: 0x%02x\t(", __field_padding, section, flags);
+
+ va_list args = void;
+ va_start(args, flags);
+ print_flagsv(flags, args);
+}
+void print_flags16(const(char) *section, ushort flags, ...) {
+ printf("%*s: 0x%04x\t(", __field_padding, section, flags);
+
+ va_list args = void;
+ va_start(args, flags);
+ print_flagsv(flags, args);
}
void print_flags32(const(char) *section, uint flags, ...) {
printf("%*s: 0x%08x\t(", __field_padding, section, flags);
va_list args = void;
va_start(args, flags);
- ushort count;
-L_START:
- const(char) *name = va_arg!(const(char)*)(args);
- if (name == null) {
- puts(")");
- return;
- }
-
- if ((flags & va_arg!int(args)) == 0) goto L_START; // condition
- if (count++) putchar(',');
- printf("%s", name);
- goto L_START;
+ print_flagsv(flags, args);
}
void print_flags64(const(char) *section, ulong flags, ...) {
printf("%*s: 0x%016llx\t(", __field_padding, section, flags);
diff --git a/dumper/format/omf.d b/dumper/format/omf.d
new file mode 100644
index 00000000..86876f92
--- /dev/null
+++ b/dumper/format/omf.d
@@ -0,0 +1,79 @@
+/// OMF dumper.
+///
+/// Authors: dd86k
+/// Copyright: © dd86k
+/// License: BSD-3-Clause-Clear
+module format.omf;
+
+import adbg.object.server;
+import adbg.object.format.omf;
+import dumper;
+
+extern (C):
+
+int dump_omf(adbg_object_t *o) {
+ if (selected_headers())
+ dump_omf_hdr(o);
+ if (selected_debug())
+ dump_omf_debug(o);
+ return 0;
+}
+
+private:
+
+void dump_omf_hdr(adbg_object_t *o) {
+ // Library
+ if (o.i.omf.firstentry) {
+ print_header("Library Header");
+ with (o.i.omf.header) {
+ print_x8("Type", type);
+ print_u16("RecordLength", size);
+ print_x32("DictionaryOffset", dicoff);
+ print_u16("DictionarySize", dicsize);
+ print_flags8("Flags", flags,
+ "CaseSensitive".ptr, OMF_LF_CS,
+ null);
+ }
+ }
+
+ print_header("First Object Entry");
+ omf_entry* entry = adbg_object_omf_entry(o, 0);
+ dump_omf_print_entry(entry);
+ adbg_object_omf_entry_free(o, entry);
+}
+
+void dump_omf_debug(adbg_object_t *o) {
+ int offset = o.i.omf.firstentry;
+
+Lentry:
+ omf_entry* entry = adbg_object_omf_entry(o, offset);
+ if (entry == null)
+ return;
+
+ print_header("Entry");
+ if (dump_omf_print_entry(entry)) // print entry
+ return;
+ offset += entry.size + 3; // advance
+ adbg_object_omf_entry_free(o, entry); // free
+ goto Lentry;
+}
+
+// print entry
+int dump_omf_print_entry(omf_entry *entry) {
+ print_x8("Type", entry.type, adbg_object_omf_type_string(entry));
+ print_u16("Size", entry.size);
+ print_x8("Checksum", entry.checksum);
+ switch (entry.type) with (OMFRecord) {
+ case THEADR, LHEADR:
+ ubyte len = (cast(ubyte*)entry.data)[0];
+ if (len >= entry.size) {
+ print_string("error", "String length outside bounds");
+ return 1;
+ }
+ const(char)* str = cast(const(char)*)entry.data + 1;
+ print_stringl("Name", str, len);
+ break;
+ default:
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/dumper/format/package.d b/dumper/format/package.d
index 239a8921..b43922e7 100644
--- a/dumper/format/package.d
+++ b/dumper/format/package.d
@@ -16,6 +16,7 @@ public import
format.pdb20,
format.mdmp,
format.dmp,
+ format.omf,
format.ar,
format.coff,
format.mscoff;
\ No newline at end of file
diff --git a/src/adbg/object/format/omf.d b/src/adbg/object/format/omf.d
new file mode 100644
index 00000000..7c9d9524
--- /dev/null
+++ b/src/adbg/object/format/omf.d
@@ -0,0 +1,273 @@
+/// 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.
+///
+/// Authors: dd86k
+/// Copyright: © dd86k
+/// 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;
+
+// Sources:
+// - Tool Interface Standards (TIS) Relocatable Object Module Format (OMF) Specification Version 1.1
+
+// 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;
+ }
+}
\ No newline at end of file
diff --git a/src/adbg/object/formats.d b/src/adbg/object/formats.d
index a9891eeb..8e532e3a 100644
--- a/src/adbg/object/formats.d
+++ b/src/adbg/object/formats.d
@@ -15,6 +15,7 @@ public import
adbg.object.format.pdb,
adbg.object.format.mdmp,
adbg.object.format.dmp,
+ adbg.object.format.omf,
adbg.object.format.ar,
adbg.object.format.coff,
adbg.object.format.mscoff;
\ No newline at end of file
diff --git a/src/adbg/object/server.d b/src/adbg/object/server.d
index fa219428..65ef579f 100644
--- a/src/adbg/object/server.d
+++ b/src/adbg/object/server.d
@@ -78,7 +78,9 @@ enum AdbgObject {
dmp,
/// Windows Minidump format. (.mdmp)
mdmp,
- /// Library archive. (.lib)
+ /// OMF object or library. (.obj, .lib)
+ omf,
+ /// COFF Library archive. (.lib)
archive,
/// COFF object. (.obj)
coff,
@@ -86,6 +88,12 @@ enum AdbgObject {
mscoff,
}
+///
+enum AdbgObjectKind {
+ unknown,
+ executable
+}
+
/// Object origin. (or "load mode")
///
/// How was the object loaded or hooked.
@@ -299,6 +307,13 @@ struct adbg_object_t {
}
dmp_t dmp;
+ struct omf_t {
+ omf_lib_header *header;
+ int pgsize;
+ int firstentry;
+ }
+ omf_t omf;
+
struct coff_t {
coff_header *header;
}
@@ -716,8 +731,19 @@ L_ARG:
case COFF_MAGIC_MIPSEL:
return adbg_object_coff_load(o);
default:
- return adbg_oops(AdbgError.objectUnknownFormat);
}
+
+ // 8-bit signature detection
+ ubyte sig8 = *o.buffer8;
+ switch (sig8) {
+ case OMFRecord.LIBRARY: // OMF library header entry
+ case OMFRecord.THEADR: // First OMF object entry of THEADR
+ case OMFRecord.LHEADR: // First OMF object entry of LHEADR
+ return adbg_object_omf_load(o, sig8);
+ default:
+ }
+
+ return adbg_oops(AdbgError.objectUnknownFormat);
}
//TODO: adbg_object_load_continue if partial was used
@@ -770,6 +796,7 @@ const(char)* adbg_object_format_shortname(adbg_object_t *o) {
case pdb70: return "pdb70";
case mdmp: return "mdmp";
case dmp: return "dmp";
+ case omf: return "omf";
case archive: return "archive";
case coff: return "coff";
case mscoff: return "mscoff";
@@ -795,6 +822,7 @@ const(char)* adbg_object_format_name(adbg_object_t *o) {
case pdb70: return `Program Database 7.0`;
case mdmp: return `Windows Minidump`;
case dmp: return `Windows Memory Dump`;
+ case omf: return `Relocatable Object Module Format`;
case archive: return `Common Object File Format Library Archive`;
case coff: return `Common Object File Format`;
case mscoff: return `Microsoft Common Object File Format`;
@@ -834,6 +862,8 @@ const(char)* adbg_object_format_kind_string(adbg_object_t *o) {
return `Memory Dump`;
case archive:
return `Library`;
+ case omf:
+ return o.i.omf.firstentry ? `Library` : `Object`;
case coff, mscoff:
return `Object`;
default: Lunknown: