So a .exe file is a file that can be executed by windows, but what exactly does it contain? Assembly language that's processor specific? Or some sort of intermediate statement that's recognized by windows which turns it into assembly for a specific processor? What exactly does windows do with the file when it "executes" it?
MSDN has an article "An In-Depth Look into the Win32 Portable Executable File Format" that describes the structure of an executable file.
Basically, a .exe contains several blobs of data and instructions on how they should be loaded into memory. Some of these sections happen to contain machine code that can be executed (other sections contain program data, resources, relocation information, import information, etc.)
I suggest you get a copy of Windows Internals for a full description of what happens when you run an exe.
For a native executable, the machine code is platform specific. The .exe's header indicates what platform the .exe is for.
When running a native .exe the following happens (grossly simplified):
- A process object is created.
- The exe file is read into that process's memory. Different sections of the .exe (code, data, etc.) are mapped in separately and given different permissions (code is execute, data is read/write, constants are read-only).
- Relocations occur in the .exe (addresses get patched if the .exe was not loaded at its preferred address.)
- The import table is walked and dependent DLL's are loaded.
- DLL's are mapped in a similar method to .exe's, with relocations occuring and their dependent DLL's being loaded. Imported functions from DLL's are resolved.
- The process starts execution at an initial stub in NTDLL.
- The initial loader stub runs the entry points for each DLL, and then jumps to the entry point of the .exe.
Managed executables contain MSIL (Microsoft Intermediate Language) and may be compiled so they can target any CPU that the CLR supports. I am not that familiar with the inner workings of the CLR loader (what native code initially runs to boot strap the CLR and start interpreting the MSIL) - perhaps someone else can elaborate on that.
I can tell you what the first two bytes in .exe files contain - 'MZ'. i mean the characters 'MZ'.
It actually represents: Mark Zbikowski. The guy who designed the exe file format.
1's and 0's!
This wikipedia link will give you all the info you need on the Portable Executable format used for Windows applications.
An EXE file is really a type of file known as a Portable Executable. It contains binary data, which can be read by the processor and executed (essentially x86 instructions.) There's also a lot of header data and other miscellaneous content. The actual executable code is located in a section called .text
, and is stored as machine instructions (processor specific). This code (as well as other parts of the .EXE) are put into memory, and the CPU is sent to it, where it starts executing. (Note that there's much more interfaces actually happening; this is a simplified explanation).
I can only answer the question for the legacy DOS version of .EXE. The Windows version (portable EXE) is substantially more involved and would take a lot more to explain - and understand. So, I'll leave that to others.
The 8086, which is what the DOS EXE was designed for, organizes its memory into 2¹⁶ segments of up to 2¹⁶ bytes each, using an addressing that is nominally 32-bits, the components an address being referred to, respectively, as the "Segment" and "Offset". An address with Segment S and Offset O would be written as S:O.
This was designed with the idea that a single source code file would be compiled into a single segment, and that intra-file accesses would use only the "Offset" part of an address, so that the full address would only be required for inter-file accesses to global objects.
The address is flattened out by being mapped to a 2²⁰ physical space in such a way that the physical address is 2⁴×Segment + Offset; i.e. so that address 0 of segment S+1 overlaps on a "paragraph" boundary in segment S at address 2⁴. In most cases, the additional detail of the flattening doesn't need to be known and is not generally used explicitly in programs, but is tacit in the layout of the EXE file. The EXE file's format is, thus, the program image seen at the "post-flattening" stage.
This 8086 architecture remained mostly intact, up to the 80186 and 80286. With the 80386, the segmentation architecture was appropriated and repurposed, though the older arrangement was kept intact in limited contexts. That's also where the change-over from DOS to Windows EXE file formats takes place.
The EXE file contains the actual binary images for the segments containing the code and initialized data. The image, itself, starts at segment 0, address 0 and the segments are laid out in the file, flattened.
When the program is loaded into memory, the "loader" will set it up starting at whatever memory segment has enough free space in and after it. Since the machine language for the 8086 makes references to absolute addresses, then the absolute addresses inside the program have to be listed in the EXE file, so that the loader can go into the program image and made the appropriate adjustments.
Thus, if a program is to be loaded at segment S₀ and a reference inside the program at location S₁:O₁ is made to segment S₂ (i.e. if the 2-byte word stored starting at the location S₁:O₁ in the program image is S₂) then it will adjust its value to S₀+S₂.
These items are called "relocations" and the table containing them, the "relocation table". Their presence and use is the main point of the EXE file format.
After the adjustments are made, it will set up the initial stack at a location specified to it by EXE file. That includes both the stack segment and its size. The stack pointer moves down with a "push" and up with a "pop" on the 8086, so the initial value of the stack pointer is over the end of the stack segment. So, the EXE file includes the initial value of SS:SP (stack segment + stack pointer).
Then it will jump to the entry point specified to it by the EXE file. So, the EXE file also includes the initial value of CS:IP (code segment + instruction pointer).
It also includes a minimum and maximum size request for program data, though I'm not sure what the loader does with this information, other than to determine whether the program can be loaded. Likewise, it also has a check sum, which I don't think is even honored, as well as an "overlay" number to distinguish between main programs and subprograms (possibly a precursor to Windows DLL's).
The mandatory part of the EXE file consists of 14 2-byte words. Be aware that all 2-byte words in the 8086 take up 2 consecutive 8-bit memory addresses, with the lower 8 bits first, followed up the upper 8 bits.
In the following description, I'll use hexadecimal numerals for everything, since that's what's most directly attuned to what needs to be described.
Labeling the mandatory words, then, by their addresses as W00, W02, W04, W06, W08, W0a, W0c, W0e, W10, W12, W14, W16, W18, W1a, the information just described is laid out as:
(1) Entry point - CS:IP = W16:W14,
(2) Stack segment - SS:SP = W0e:W10,
(3) Relocation table location W18 (= RB in the portable EXE file format specification) in the EXE file, and number of relocations W06,
(4) The starting point in the file for the code image is at: 2⁴×W08,
(5) The overlay number is W1a, which is 0 for "main" programs - the only use-case I've ever seen,
(6) The checksum is W12,
(7) The range of requests for the program data is 2⁴×W0a to 2⁴×W0c. The only use-case I've seen for W0c is to set it to max size, ffff.
(8) The size of the program file is 2⁹ (W04 - 1) + W02, if W02 > 0, else 2⁹ W04, if W02 = 0,
(9) W00 = 5a4d is the file type "signature" which, listed in increasing order of addresses is 4d and 5a, which (in ASCII) are the letters 'M' and 'Z' for the programmer Mark Zbikowski, who was probably to go-to guy who did all the dirty work in the early days of Microsoft and whose conversations and arguments with Bill Gates in that garage of theirs in the 1970's, I can see and hear the echoes of, when I look at the binaries of some of their older programs.
Each item of the relocation table contains the S:O address of the item to be relocated, with O listed first, then S. So, the size of a relocation table, in bytes, is 4 times its number of entries.
In an example - of an actual program - the relocation table of 002b entries (or 00ac bytes) is located in the EXE file at 001e-00ca. The program image has a size of 548e bytes, with the segments flattened out and placed in the file at 0200-548d . The stack is to be initialized at SS:SP = 06c1:0800 and the entry point is to be at CS:IP = 0000:05d0. The data request range is 2190-ffff0 bytes and the check sum is e3d8.
Thus, the layout of the mandatory 16-bit words, is W00 = 5a4d,
(W02, W04) = (008e, 002b),
W06 = 002b,
W08 = 0020,
(W0a, W0c) = (0219, ffff),
(W0e, W10) = (06c1, 0800),
W12 = e3d8,
(W14, W16) = (05d0, 0000),
W18 = 001e,
W1a = 0000
As a byte-sequence, this reads: 4d, 5a, 8e, 00, 2b, 00, 2b, 00, 20, 00, 19, 02, ff, ff, c1, 06, 00, 08, d8, e3, d0, 05, 00, 00, 1e, 00, 00, 00
There is a coverage hole at file locations 001c-001d, since the relocations start at file location 001e. The relocation table is at file locations 001e-00c9 and has the form, as a byte sequence:
22, 00, 00, 00,
2e, 00, 00, 00,
...
63, 39, 35, 01
which consists of the words 0022, 0000, 002e, 0000, ..., 3963, 0135, for the addresses 0000:0022, 0000:002e, ..., 0135:3963 in the program image.
Several segments listed in the relocations, including 0000, 0135, 04d0, 04fe, 0500, which is one way you can obtain some information on how the code image is to be partitioned into segments - the EXE file does not explicitly spell out the individual segments, since they're already flattened out.
There's another coverage gap from file locations 00ca to 01ff, and the code image starts at file locations 0200 up to the end of the file at 548d.
The actual segments just mentioned are mapped in the file at
0200-154f for segment 0000
1550-4eff for segment 0135
4f00-51df for segment 04d0
51e0-51ff for segment 04fe
5200-548d for segment 0500
assuming these were the only segments in the program. When the program is overlaid into memory, it will be put at 0000-528d at the first available address in physical memory, after relocations are done.
So, for instance, if it is relocated by the loader to segment 077a (which my version of DosBox) does, then the loader will have to made adjustments first. So, for instance, the word listed in the code image at 0000:0022 (and located in the file at 0222-0223) contains the word 0135 (indicating segment 0135) and has to be bumped up to 0135+077a = 08af. The segments, themselves are likewise bumped up, as part of the relocation process, from 0000, 0135, 04d0, 04fe, 0500 (and 06c1 for the stack) respectively to 077a, 08af, 0c4a, 0c78, 0c7a (and 0e3b for the stack).
© 2022 - 2025 — McMap. All rights reserved.