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: