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}