tmp/
msdosmz.rs

1//! MS-DOS MZ Executable Format
2//!
3//! The MS-DOS-MZ-Executable format was introduced with Microsoft DOS-2.0 and
4//! replaced the plain COM-format. It defines how an executable is to be loaded
5//! into memory, its required relocations, and the initial register state when
6//! starting execution. Its name was derived from the signature used by the
7//! format.
8//!
9//! The format is rarely used today. Its most common use is as part of stub
10//! programs embedded in its successor formats like PE (Portable Executable).
11//!
12//! Applications linked in the original MS-DOS format are often called "DOS COM
13//! Programs", while applications linked with the format described here are
14//! commonly referred to as "DOS EXE Programs".
15//!
16//! The format is highly tied to how programs were executed on old x86 machines
17//! running the DOS operating system. Memory segmentation in particular plays
18//! an important role in many of the design decisions. A full understanding of
19//! the MS-DOS MZ Executable Format requires at least basic understanding of
20//! how MS-DOS works. Followingly, a list of notes that might be of value to
21//! understand the intricacies of MS-DOS executables:
22//!
23//!  * Memory was organized in 64KiB segments, but segments are overlapping and
24//!    offset by 16 bytes from each other. Hence, the total addressable memory
25//!    is 1MiB (actually based on the 20-bit address-bus used in old x86
26//!    hardware).
27//!    Due to architectual decisions, only the lower 640KiB were allocated to
28//!    the application, while the upper memory was reserved for video hardware
29//!    or other hardware management.
30//!    Newer hardware supported all kinds of extensions to provide access to
31//!    memory beyond 1MiB, yet those extensions have little effect on the
32//!    design decisions of this format and thus are mostly ignored here.
33//!
34//!  * Original DOS COM applications had a hard-coded entry-point at 0x100 and
35//!    only supported addressing a single segment (64KiB). The new DOS EXE
36//!    applications support applications spanning multiple segments, as well as
37//!    any entry-point.
38//!    The initial offset of 0x100 was due to the Program Segment Prefix (PSP)
39//!    being placed in front of the application. This structure contained
40//!    information for the application, including its environment and
41//!    command-line arguments.
42//!
43//!  * While MS-DOS had different styles of programs running in parallel to
44//!    serve hardware interrupts and manage the system, it effectively was a
45//!    single-tasking OS that only ran a single application at once. However,
46//!    it is common for programs to load other programs and resume execution
47//!    once this program returned. Thus, multiple programs will be loaded in
48//!    memory at a time.
49//!
50//!  * On application startup, the loader calculates the size of the program to
51//!    load into memory, as well as the minimum required size of additional
52//!    memory beyond that, as noted in the file header. It then tries to find
53//!    the biggest block of memory that fulfils that need, up to a maximum as
54//!    specified in the file header again. This allows applications to specify
55//!    a minimum required space beyond the code stored in the file, as well as
56//!    ask for additional optional memory serving as heap memory.
57//!
58//!  * A new application is loaded into memory with its PSP directly in front
59//!    of it. The start-segment is the first segment after the PSP, and thus
60//!    the first segment holding the application. The start-segment is used to
61//!    relocate DOS EXE executables, and allows executables to be loaded at any
62//!    segment, while still spanning multiple segments.
63//!
64//!  * Old DOS executables assumed that any higher segment than their start
65//!    segment is also available to them, and thus assumed anything in the
66//!    range [CS:0x0000; 0xa000:0x0000] (i.e., anything from the start-segment
67//!    up to the highest segment at 640KiB) is available to them. This is not
68//!    strictly true, though, since high memory can be reserved for other
69//!    reasons. Hence, an application can query the PSP for the first segment
70//!    beyond the area allocated to the application to get an authoritative
71//!    answer.
72//!
73//!  * While dealing with old MS-DOS formats, there are 3 important sizes used
74//!    to measure memory areas:
75//!
76//!     * `WORD`: A word is a 16-bit integer. `DWORD` and `QWORD` refer to
77//!       double and quadruple of that size.
78//!
79//!     * `PARAGRAPH`: A paragraph are 16 consecutive bytes. This is most often
80//!       used to allocate blocks of memory, or perform operations on large
81//!       blocks. The size reflects the offset between segments on x86.
82//!
83//!     * `PAGE`: A page is 512 consecutive bytes. This is not to be confused
84//!       with page-sizes on modern x86 hardware (often 4k).
85//!
86//!  * All multi-byte integers are encoded as little-endian.
87//!
88//! The file-format is rather trivial. It consists of a static header, which
89//! embeds its own size and thus allows for applications to store additional
90//! private data trailing the static header. Additionally, the file contains a
91//! relocation table with all address-relocations that are to be applied to the
92//! application code before execution. This table is usually placed directly
93//! after the header, including its size in the total header size.
94//!
95//! Anything beyond the header is considered application code, up to the total
96//! size as specified in the header. Anything trailing that is not considered
97//! part of the executable and ignored.
98//!
99//! There is a Microsoft header extension which is used to designate the file
100//! as a DOS-compatibility executable. This header must directly follow the
101//! static header, and the relocation-offset usually indicates that the
102//! relocation table follows this extended header, thus showing that such an
103//! extended header is likely present. The extended header does not affect the
104//! executable, but contains metadata describing other data stored after the
105//! code (which is ignored by the DOS EXE loader). This technique is usually
106//! used to combine a DOS EXE executable with a more modern PE executable into
107//! a single file.
108
109use core::mem::{
110    align_of,
111    align_of_val,
112    size_of,
113    size_of_val,
114};
115
116type U16Le = osi::ffi::Integer<osi::ffi::LittleEndian<u16>, osi::align::AlignAs<2>>;
117type U32Le = osi::ffi::Integer<osi::ffi::LittleEndian<u32>, osi::align::AlignAs<4>>;
118
119/// Size of a Word
120///
121/// The term `WORD` is used to denote 2-byte integers in MS-DOS software, and
122/// many derivatives. It reflects the size of the data-bus and registers of the
123/// originally supported x86 machines.
124pub const WORD_SIZE: usize = 2;
125
126/// Size of a Paragraph
127///
128/// The term `PARAGRAPH` is used to denote 16-byte memory regions in MS-DOS
129/// software and some derivatives. It reflects the offset of two consecutive
130/// segments on x86 machines in Real Mode. Note that segments are overlapping,
131/// thus a paragraph describes their relative distance but not their size.
132pub const PARAGRAPH_SIZE: usize = 16;
133
134/// Size of a Page
135///
136/// The term `PAGE` usually denotes 512-byte memory regions in MS-DOS software.
137/// It reflects the size of hardware pages used to store memory on disk. Hence,
138/// for better performance, in-memory data is often also organized in pages to
139/// allow easy mapping to storage pages.
140pub const PAGE_SIZE: usize = 512;
141
142/// Magic Signature
143///
144/// The initial 2 bytes of every DOS-MZ file are set to `0x4d` followed by
145/// `0x5a` ("MZ"). They are used to identify the format. They reflect the
146/// initials of an early Microsoft employee who worked on the format.
147pub const MAGIC: [u8; 2] = [0x4d, 0x5a];
148
149/// Calculate 16-bit Sum
150///
151/// This function splits a byte slice into consecutive 16-bit unsigned integers
152/// and calculates their sum. Little endianness is assumed. If the slice is of
153/// an odd length, a missing trailing zero byte is assumed.
154///
155/// The sum is calculated in wrapping-mode, meaning overflows are handled
156/// gracefully.
157///
158/// This function is tailored for checksum calculations, which commonly are
159/// based on the 16-bit sum of a byte slice.
160pub fn sum16(data: &[u8]) -> u16 {
161    data.chunks(2).fold(
162        0u16,
163        |acc, values| {
164            acc.wrapping_add(
165                match values.len() {
166                    1 => { values[0] as u16 },
167                    2 => { u16::from_le_bytes(values.try_into().unwrap()) },
168                    _ => { unreachable!(); },
169                }
170            )
171        },
172    )
173}
174
175/// File Header
176///
177/// This static structure is located at offset 0 of a DOS MZ executable. It
178/// has a fixed size of 28 bytes and describes the further layout of the file.
179#[repr(C)]
180pub struct Header {
181    /// The static signature identifying the file-format. This must match
182    /// `MAGIC`. (abbr: magic-identifier).
183    pub magic: [u8; 2],
184
185    /// The number of bytes in the last page of the program (abbr:
186    /// count-bytes-last-page)
187    ///
188    /// While `cp` describes the number of pages that make up this executable,
189    /// this `cblp` field describes the number of valid bytes in the last page.
190    /// The special value of `0` denotes the entire last page as part of the
191    /// executable.
192    ///
193    /// Any other data beyond this offset is ignored. It is valid to append
194    /// data for other reasons to the end of the file.
195    pub cblp: U16Le,
196
197    /// The number of pages of this program (abbr: count-pages)
198    ///
199    /// This is the absolute number of pages starting from the beginning of
200    /// the header that make up this executable. Any data beyond this is
201    /// ignored. The `cblp` field describes how many bytes of the last page
202    /// are actually part of the program.
203    ///
204    /// E.g., A `cp` of 2 with `cblp` of 16 means `(2-1)*512+16=528` bytes. A
205    /// `cp` of 3 with `cblp` of 0 means `3*512=1536` bytes.
206    pub cp: U16Le,
207
208    /// The number of relocations in the relocation header (abbr:
209    /// count-re-lo-cations)
210    ///
211    /// In combination with `lfarlc` this describes the location and size of
212    /// the relocation table. The `crlc` field is the number of relocation
213    /// entries in this table. See `lfarlc` for details.
214    pub crlc: U16Le,
215
216    /// The number of paragraphs that make up the header (abbr:
217    /// count-paragraphs-header).
218    pub cparhdr: U16Le,
219
220    /// The minimum number of additional paragraphs required for program
221    /// execution (abbr: minimum-allocation).
222    ///
223    /// When a program is loaded into memory, its program size is the file
224    /// size as specified by `cp` and `cblp` minus the size of the header
225    /// as specified by `cparhdr`. This size is then extended by `minalloc`
226    /// to calculate the minimum amount of memory required to execute the
227    /// program.
228    ///
229    /// This technique allows linkers to strip uninitialized static
230    /// variables from the program code, and thus reduce the size of the
231    /// file. The variables are then located in the allocated space
232    /// trailing the program code, and thus still available at runtime.
233    pub minalloc: U16Le,
234
235    /// The maximum number of additional paragraphs required for program
236    /// execution (abbr: maximum-allocation).
237    ///
238    /// While `minalloc` is a minimum requirement to allow execution of a
239    /// program, `maxalloc` specifies how much more memory the application
240    /// would like allocated. The OS is free to allocate more or less. The
241    /// PSP contains information about how much was actually allocated.
242    ///
243    /// `maxalloc` cannot be less than `minalloc`, or it will be ignored.
244    ///
245    /// A value of `0xffff` simply means to allocate as much memory as
246    /// possible. The application is still free to use MS-DOS software
247    /// interrupts to deallocate memory again.
248    pub maxalloc: U16Le,
249
250    /// The initial stack-segment offset to use (abbr: stack-segment).
251    ///
252    /// The stack-segment register is set to the same value as the code-segment
253    /// register at program start. The latter is set to the first segment of
254    /// the program loaded in memory. In front of this first segment is usually
255    /// the PSP (the `DS` and `ES` registers contain the segment of the PSP at
256    /// program start, to avoid relying on this).
257    ///
258    /// This `ss` value is an offset added to the stack-segment register at
259    /// program start, and thus allows moving the stack into later segments.
260    pub ss: U16Le,
261
262    /// The initial stack pointer to use (abbr: stack-pointer).
263    ///
264    /// This specifies the initial value of the `sp` register. In combination
265    /// with `ss` it defines the start of the stack. In most cases, this value
266    /// is set to `0`, since this will cause the first stack push to overflow
267    /// the value and thus push at the end of the stack segment.
268    pub sp: U16Le,
269
270    /// The checksum of the file (abbr: check-sum).
271    ///
272    /// The checksum of the file is the one's complement of the summation of
273    /// all words of the file (with `csum` set to 0). Note that this only
274    /// applies to the data as specified by `cp` and `cblp`. Any trailing data
275    /// is ignored. If `cblp` is odd, the last byte trailing it must be assumed
276    /// to be 0, even if other data is trailing it.
277    ///
278    /// The checksum is calculated as little-endian sum of all 16-bit integers
279    /// that make up the file.
280    pub csum: U16Le,
281
282    /// The initial instruction pointer to use (abbr:
283    /// instruction-pointer).
284    ///
285    /// This is the initial value of the instruction pointer register `ip`.
286    /// Since it is relative to the code-segment register `cs`, this value is
287    /// taken verbatim by the loader.
288    pub ip: U16Le,
289
290    /// The initial code segment offset to use (abbr: code-segment).
291    ///
292    /// Similar to the stack-segment register `ss`, this value defines the
293    /// offset to apply to the code-segment register `cs` before executing the
294    /// code. The actual value before the offset is applied is the first
295    /// segment the program was loaded into.
296    pub cs: U16Le,
297
298    /// The absolute offset to the relocation table (abbr:
299    /// logical-file-address-re-lo-cation).
300    ///
301    /// This is the offset of the relocation table relative to the start of the
302    /// header (and thus usually the start of the file). The relocation table
303    /// is an array of relocations (see `Relocation`). The number of entries is
304    /// specified in `crlc`.
305    ///
306    /// The relocation table is usually trailing the static header and included
307    /// in the size of the header. However, an application is free to place the
308    /// table at any other offset.
309    ///
310    /// The size of the static header plus the extension header is `0x40`,
311    /// hence a relocation table offset of `0x40` usually designates the
312    /// existance of an extension header (yet this is not a requirement). The
313    /// extension header still needs its own signature verification to ensure
314    /// its validity.
315    pub lfarlc: U16Le,
316
317    /// The overlay number (abbr: overlay-number).
318    ///
319    /// This is `0` if the file is not an overlay. Otherwise, this specifies
320    /// the overlay index to assign.
321    ///
322    /// Overlays are used to reserve programs but avoid loading them into
323    /// memory. See the MS-DOS overlay support for details. For any regular
324    /// application, this is set to `0`.
325    pub ovno: U16Le,
326}
327
328/// Extended Header
329///
330/// The extended header optionally follows the static header without any
331/// padding. The presence of an extended header is suggested by the relocation
332/// offset being beyond the extended header, as well as the header size being
333/// big enough to include the extended header.
334///
335/// The only meaningful field of the extended header is `lfanew`, which is a
336/// 32-bit offset into the file where further header information can be found.
337/// Depending on the format that uses this extended header, a different
338/// signature can be found at that offset.
339///
340/// The other fields of this extended header are very scarcely documented and
341/// thus usually set to 0.
342#[repr(C)]
343pub struct HeaderExt {
344    /// Reserved field which must be cleared to 0, yet must not be relied on
345    /// to be 0.
346    pub res: [u8; 8],
347
348    /// OEM ID, usually cleared to 0.
349    pub oemid: U16Le,
350
351    /// OEM Information, usually cleared to 0.
352    pub oeminfo: U16Le,
353
354    /// Reserved field which must be cleared to 0, yet must not be relied on
355    /// to be 0.
356    pub res2: [u8; 20],
357
358    /// File offset of the new file format (abbr: logical-file-address-new)
359    ///
360    /// This contains an offset into the file relative to the start of the
361    /// static header where to find a newer format of this file. No further
362    /// information can be deduced from this.
363    ///
364    /// Any format using this must place its own signature at the specified
365    /// offset and thus allow separate verification of its validity. In
366    /// particular, Portable Executable (PE) files will place "PE\0\0" at
367    /// this offset to denote a PE/COFF header.
368    pub lfanew: U32Le,
369}
370
371/// Relocation Information
372///
373/// This structure describes an entry of the relocation table. Each entry
374/// points into the program code, at a 2-byte value that must be adjusted with
375/// the start-segment before the program is run. The value of the start-segment
376/// is simply added to each location pointed at by the relocation table.
377///
378/// A single location is described by its segment relative to the start of the
379/// program, as well as the offset inside that segment.
380#[repr(C)]
381pub struct Relocation {
382    /// Offset of the relocation target relative to the specified segment.
383    pub offset: U16Le,
384
385    /// Segment of the relocation target relative to the start of the code.
386    pub segment: U16Le,
387}
388
389impl Header {
390    /// Import a header from a byte slice
391    ///
392    /// Create a new header structure from a byte slice, copying the data over.
393    /// The data is copied verbatim without any conversion.
394    pub fn from_bytes(data: &[u8; 28]) -> Self {
395        let mut uninit: core::mem::MaybeUninit<Self> = core::mem::MaybeUninit::uninit();
396
397        assert!(align_of_val(data) <= align_of::<Self>());
398        assert!(size_of_val(data) == size_of::<Self>());
399
400        unsafe {
401            // Safety: The entire struct consists of unsigned integers and
402            //         arrays of unsigned integers, which all have no invalid
403            //         byte-level representations and thus can be imported
404            //         directly. Even the wrong endianness is still a valid
405            //         value.
406            core::ptr::write(uninit.as_mut_ptr() as *mut [u8; 28], *data);
407            uninit.assume_init()
408        }
409    }
410
411    /// Convert to byte slice
412    ///
413    /// Return a byte-slice reference to the header. This can be used to export
414    /// the structure into a file. No byte-order conversions are applied.
415    pub fn as_bytes(&self) -> &[u8; 28] {
416        assert!(align_of::<[u8; 28]>() <= align_of::<Self>());
417        assert!(size_of::<[u8; 28]>() == size_of::<Self>());
418
419        unsafe {
420            core::mem::transmute::<&Self, &[u8; 28]>(self)
421        }
422    }
423}
424
425impl HeaderExt {
426    /// Import a header extension from a byte slice
427    ///
428    /// Create a new header extension structure from data copied from a byte
429    /// slice. No byte-order conversions are applied.
430    pub fn from_bytes(data: &[u8; 36]) -> Self {
431        let mut uninit: core::mem::MaybeUninit<Self> = core::mem::MaybeUninit::uninit();
432
433        assert!(align_of_val(data) <= align_of::<Self>());
434        assert!(size_of_val(data) == size_of::<Self>());
435
436        unsafe {
437            core::ptr::write(uninit.as_mut_ptr() as *mut [u8; 36], *data);
438            uninit.assume_init()
439        }
440    }
441
442    /// Convert to byte slice
443    ///
444    /// Return a byte-slice reference to the header extension. This can be used
445    /// to export the structure into a file. No byte-order conversions are
446    /// applied.
447    pub fn as_bytes(&self) -> &[u8; 36] {
448        assert!(align_of::<[u8; 36]>() <= align_of::<Self>());
449        assert!(size_of::<[u8; 36]>() == size_of::<Self>());
450
451        unsafe {
452            core::mem::transmute::<&Self, &[u8; 36]>(self)
453        }
454    }
455}
456
457impl Relocation {
458    /// Import a relocation entry from a byte slice
459    ///
460    /// Create a new relocation structure from data copied from a byte
461    /// slice. No byte-order conversions are applied.
462    pub fn from_bytes(data: &[u8; 4]) -> Self {
463        let mut uninit: core::mem::MaybeUninit<Self> = core::mem::MaybeUninit::uninit();
464
465        assert!(align_of_val(data) <= align_of::<Self>());
466        assert!(size_of_val(data) == size_of::<Self>());
467
468        unsafe {
469            core::ptr::write(uninit.as_mut_ptr() as *mut [u8; 4], *data);
470            uninit.assume_init()
471        }
472    }
473
474    /// Convert to byte slice
475    ///
476    /// Return a byte-slice reference to the relocation entry. This can be used
477    /// to export the structure into a file. No byte-order conversions are
478    /// applied.
479    pub fn as_bytes(&self) -> &[u8; 4] {
480        assert!(align_of::<[u8; 4]>() <= align_of::<Self>());
481        assert!(size_of::<[u8; 4]>() == size_of::<Self>());
482
483        unsafe {
484            core::mem::transmute::<&Self, &[u8; 4]>(self)
485        }
486    }
487}
488
489/// X86 Stub Program
490///
491/// This array contains a full MS-DOS EXE program that prints the following
492/// line on startup and then exits with an error code of 1:
493///
494///   "This program cannot be run in DOS mode.\r\r\n"
495///
496/// A stub program like this is typically used with extended file-formats like
497/// PE/COFF.
498///
499/// This stub is a fully functioning DOS program of size 128 bytes. It contains
500/// an extended DOS header with the `lfanew` offset set to 128 (directly after
501/// this stub). Hence, you can prepend this 128-byte stub to any PE program
502/// without any modifications required. If required, the `lfanew` offset can
503/// be adjusted after copying it.
504pub const STUB_X86: [u8; 128] = [
505    // Header:
506    0x4d, 0x5a, //              MAGIC
507    0x80, 0x00, //              cblp: 128
508    0x01, 0x00, //              cp: 1
509    0x00, 0x00, //              crlc: 0 (no relocations)
510    0x04, 0x00, //              cparhdr: 4 (64 bytes; header+ext)
511    0x00, 0x00, //              minalloc: 0
512    0xff, 0xff, //              maxalloc: 0xffff
513    0x00, 0x00, //              ss: 0
514    0x80, 0x00, //              sp: 0x80 (128; 64 code + 64 stack)
515    0x68, 0xa7, //              csum: 0xa768
516    0x00, 0x00, //              ip: 0
517    0x00, 0x00, //              cs: 0
518    0x40, 0x00, //              lfarlc: 0x40 (64; directly after the ext-header)
519    0x00, 0x00, //              ovno: 0
520
521    // HeaderExt:
522    0x00, 0x00, 0x00, 0x00, //  reserved
523    0x00, 0x00, 0x00, 0x00, //  reserved
524    0x00, 0x00, //              oemid: 0
525    0x00, 0x00, //              oeminfo: 0
526    0x00, 0x00, 0x00, 0x00, //  reserved
527    0x00, 0x00, 0x00, 0x00, //  reserved
528    0x00, 0x00, 0x00, 0x00, //  reserved
529    0x00, 0x00, 0x00, 0x00, //  reserved
530    0x00, 0x00, 0x00, 0x00, //  reserved
531    0x80, 0x00, 0x00, 0x00, //  lfanew: 0x80 (128; directly after this stub)
532
533    // Program:
534
535    0x0e, //                    push cs         (save CS)
536    0x1f, //                    pop ds          (set DS=CS)
537    0xba, 0x0e, 0x00, //        mov dx,0xe      (point to string)
538    0xb4, 0x09, //              mov ah,0x9      (number of "print"-syscall)
539    0xcd, 0x21, //              int 0x21        (invoke syscall)
540    0xb8, 0x01, 0x4c, //        mov ax,0x4c01   (number of "exit-1"-syscall)
541    0xcd, 0x21, //              int 0x21        (invoke syscall)
542
543    //                          "This program cannot be run in DOS mode."
544    //                          "\r\r\n$"
545    0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f,
546    0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e,
547    0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72,
548    0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f,
549    0x53, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d,
550    0x0d, 0x0a, 0x24,
551
552    // Alignment to 8-byte boundary and 128-byte total.
553    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
554];
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    // Verify alignment and size of our protocol types match the values
561    // provided by the specification.
562    #[test]
563    fn verify_types() {
564        assert_eq!(size_of::<Header>(), 28);
565        assert_eq!(align_of::<Header>(), 2);
566
567        assert_eq!(size_of::<HeaderExt>(), 36);
568        assert_eq!(align_of::<HeaderExt>(), 4);
569
570        assert_eq!(size_of::<Header>() + size_of::<HeaderExt>(), 64);
571
572        assert_eq!(size_of::<Relocation>(), 4);
573        assert_eq!(align_of::<Relocation>(), 2);
574    }
575
576    // Basic test for the Header API.
577    #[test]
578    fn verify_header() {
579        let h = Header::from_bytes((&STUB_X86[..28]).try_into().unwrap());
580
581        assert_eq!(h.magic, MAGIC);
582
583        assert_eq!(h.as_bytes(), &STUB_X86[..28]);
584    }
585
586    // Basic test for the HeaderExt API.
587    #[test]
588    fn verify_headerext() {
589        let e = HeaderExt::from_bytes((&STUB_X86[28..64]).try_into().unwrap());
590
591        assert_eq!(e.lfanew.to_native(), 0x0080);
592
593        assert_eq!(e.as_bytes(), &STUB_X86[28..64]);
594    }
595
596    // Basic test for the Relocation API.
597    #[test]
598    fn verify_relocation() {
599        let r_slice: [u8; 4] = [0x10, 0x00, 0x20, 0x00];
600        let r = Relocation::from_bytes(&r_slice);
601
602        assert_eq!(r.offset.to_native(), 0x0010);
603        assert_eq!(r.segment.to_native(), 0x0020);
604
605        assert_eq!(r.as_bytes(), &r_slice);
606    }
607
608    // Test the `sum16()` helper, including overflow checks, endianness
609    // verification, and correct slice splitting.
610    #[test]
611    fn verify_sum16() {
612        // Sum up 0+1+2+3.
613        let data = [0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00];
614        assert_eq!(sum16(&data), 6);
615
616        // Sum up 0+1+2+3 with a missing trailing byte.
617        let data = [0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03];
618        assert_eq!(sum16(&data), 6);
619
620        // Verify high values are correctly added.
621        let data = [0x01, 0x02, 0x02, 0x04];
622        assert_eq!(sum16(&data), 0x0603);
623
624        // Verify an overflow is calculated correctly.
625        let data = [0xff, 0xff, 0x02, 0x00];
626        assert_eq!(sum16(&data), 1);
627    }
628
629    // Verify the contents of the x86-stub and make sure the decoder produces
630    // the expected values.
631    #[test]
632    fn verify_stub_x86() {
633        let stub = &STUB_X86;
634        let h = Header::from_bytes((&stub[..28]).try_into().unwrap());
635        let e = HeaderExt::from_bytes((&stub[28..64]).try_into().unwrap());
636
637        // Verify expected header values.
638        assert_eq!(h.magic, MAGIC);
639        assert_eq!(h.cblp.to_native(), 128);
640        assert_eq!(h.cp.to_native(), 1);
641        assert_eq!(h.crlc.to_native(), 0);
642        assert_eq!(h.cparhdr.to_native(), 4);
643        assert_eq!(h.minalloc.to_native(), 0x0000);
644        assert_eq!(h.maxalloc.to_native(), 0xffff);
645        assert_eq!(h.ss.to_native(), 0x00);
646        assert_eq!(h.sp.to_native(), 0x80);
647        assert_eq!(h.csum.to_native(), 0xa768);
648        assert_eq!(h.ip.to_native(), 0);
649        assert_eq!(h.cs.to_native(), 0);
650        assert_eq!(h.lfarlc.to_native(), 0x40);
651        assert_eq!(h.ovno.to_native(), 0x00);
652
653        // Verify expected extended header.
654        assert_eq!(e.res, [0; 8]);
655        assert_eq!(e.oemid.to_native(), 0x00);
656        assert_eq!(e.oeminfo.to_native(), 0x00);
657        assert_eq!(e.res2, [0; 20]);
658        assert_eq!(e.lfanew.to_native(), 0x0080);
659
660        // Verify the checksum-field is correct.
661        assert_eq!(!sum16(stub), 0);
662    }
663}