12

Is there some simple method for determining if a DOS (or OS/2, or Windows etc.) binary (.exe or .dll) is 16-bit or 32-bit? The Linux file command just says "executable".

I want to distinguish between programs that run on any "x86" processor, and those that "require a 386", in a way that is simpler than setting up two environments and attempting to run the program.

And what I want is a tool, like the file command, not primarily more detailed technical explanations.

1
  • 1
    Comments are not for extended discussion; this conversation has been moved to chat.
    – Chenmunka
    Feb 17 at 16:50
1

There is in the windows nt resource kit, a program called 'exetype'. The 3.1 version is a DOS program, while liter ones, like 3.51, is a win32 program. You type exetype filename.ext to get its type. It even tells you whether it's a vio (command line), or PM program.

6
47

Plain DOS executables, in either COM or MZ format, don’t provide this information in their headers (when there is one — COM format doesn’t have a header). The only reliable way to determine whether a program requires a given CPU is to try running it on some less capable system (or emulation, e.g. with PCem which has accurate emulations of different x86 processors), and rely on either the program checking that the CPU is good enough for it, or on it crashing with an invalid opcode (on a 186 or later). Alternatively, you could try disassembling the program and looking for 386+ opcodes; but that’s only as reliable as the disassembly process, and will give inconsistent results on programs which adapt to the CPU (running 8086 code only on 8086s, 386 code on 386s, etc.).

For protected-mode executables specifically, you could look for one of the common 32-bit DOS extenders or stubs, e.g. DOS/4G, 32RTM... A program using one of these will require a 386. (There are also 286 extenders.) See Are .COM executable binaries real mode or protected mode? for more discussion of how DOS programs start (but real- v. protected-mode is only one aspect of instruction set usage; programs can use 286, 386, 486, Pentium etc. instructions even in real mode).

Many post-MZ executable formats encode this information; for example, 16-bit Windows and OS/2 executables are “new executables”, with a flag indicating which CPU is required. 32-bit OS/2 executables are “linear executables”, identified by an LX header following the MZ stub, and specify their target CPU and operating system. Likewise, 32-bit Windows programs are “portable executables”, and the PE header identifies the target CPU. file knows about these and will identify them correctly. However, it’s worth checking the details of the MZ stub too, since LX or PE executables can contain a 16-bit variant in their MZ stub, and would therefore be capable of running on a 16-bit CPU.

5
  • 3
    Yes, I understand you’re after a simple tool, but AFAIK there isn’t one. There are emulators of only 16-bit PCs; for example PCem has to be configured with a given CPU, and if set to a 286 it won’t emulate any 386 instructions. Feb 14 at 16:39
  • 7
    WRT running under emulation - to be really sure there are no 32-bit instructions, one would need to execute every possible code path under that emulation. I think it's clear that file doesn't want to solve the Halting Problem. Feb 15 at 14:57
  • 2
    @Toby not necessarily – you don’t want to run the code paths on the 32-bit side after a CPU check, if there’s a 16-bit side ;-). But yes, I agree it’s impossible to get a definitive statement, and one ends up relying on the realities of computers at the time: a program which crashed with an invalid opcode when run on a 286, because it didn’t bother checking it was running on a 386, wouldn’t be too popular. Feb 15 at 15:00
  • 2
    I'd argue that the 32-bit side of a test isn't a possible code path in the emulated environment! But, especially in early personal computers, programmers tended to play fast and loose with self-modifying code (such as self-uncompressing binaries) and worse. I think we're in violent agreement - it's really not possible by mere inspection. Feb 15 at 15:08
  • 2
    @Toby oh yes I agree we’re in violent agreement, I was being facetious. I have a program somewhere which does code-path analysis to try to determine the type of CPU required, but even then the only thing it claims is to attempt to determine the maximum CPU which may be required, not the minimum. Feb 15 at 15:15
17

There is no easy way.

The original DOS "MZ" type executable header do not contain such information about what kind of code it contains or what CPU type it needs. It just contains a binary image that is loaded to memory and information about how to start it in real mode, so there are no separate 16-bit or 32-bit binaries.

The binary image may contain any real mode opcodes that are available for a given type of CPU, but if it does contain 32-bit protected mode code, there will be a real mode loader stub to switch to protected mode and jump to run the 32-bit image contained in the executable file.

So DOS cannot know what the executable contains, DOS simply loads the executable binary into memory, does the relocations, and jumps to run the code at the entry point given in the headers. It is up to the executable what opcodes it contains, and for example if it needs a certain type of CPU to run certain opcodes, it can perform the CPU type detection if it is suitable for running the rest of the program. The executable might for example contain a stub to put the 386 CPU into 32-bit protected mode and then execute most of the program in that mode instead of 16-bit real mode.

So in order to detect what type of CPU is needed, the program has to be for example run in a virtual machine that sees what opcodes are executed. A disassembler can also be used to see what opcodes there is in the binary. While there might be standard compiler startup codes that could be detected by a tool analyzing the generated binary, there are also infinitely many other ways to make a program that does not use standard compilers or libraries, or that can make the disassembler to not be able to follow each and every code path, for example by dynamically generating a pointer where to go execute code.

So, it is not impossible. It is just not easy.

28
  • 6
    Why not? If I just add because of what I said, there is no easy way to determine if the executable contains 32-bit code or not, without disassembling it or running it under an emulator to figure it out?
    – Justme
    Feb 14 at 15:05
  • 8
    You are asking about DOS executables. DOS cannot know what the executable contains, DOS simply loads the executable binary into memory and jumps to run the code at the given entry point. It is up to the executable what opcodes it contains, and for example detect the CPU type if it is suitable for running the rest of the program. The executable might for example contain a stub to put the 386 CPU into 32-bit protected mode and then execute most of the program in that mode instead of 16-bit real mode.
    – Justme
    Feb 14 at 15:25
  • 2
    @Justme - re there is no easy way... IMO if you added that to your answer, it would be complete. One can infer that as it stands, but explicit is better. Feb 14 at 15:45
  • 6
    No, simply stop at the first 386-specific instruction. The ones that are not 386-specific instructions, the same opcode work differently in different CPU modes. The same data move opcode will move 16 bits in real mode, and 32 bits in protected mode. The file command does not include a heuristic x86 disassembler to determine it. That is almost same as if the file command could determine if a JPG file contains a picture of a cat or a dog.
    – Justme
    Feb 14 at 18:04
  • 6
    @Justme To add more complications: many programs were compressed and unpacked after loading (either to save disk space and/or to make tampering more difficult). A simple "file scan" wouldn't even see the op-codes of the main program, only of the unpacker.
    – TripeHound
    Feb 14 at 23:42
9

Is there some simple method for determining if a DOS binary (.exe or .dll) is 16-bit or 32-bit?

For one, DOS doesn't know about 32 bit, it's a strict 16 bit system. Second, .DLL are not DOS executables but Windows libraries.

The Linux file command just says "executable".

Because all EXE start out as 16 bit programs, marked by the magic number "MZ" in the first two bytes. DLLs as well, as they carry a stub header of a 16 Bit EXE. This is done to enable a warning (*1) in case someone tries hard enough to start it under DOS. So for all practical purpose they are 16 bit executables.

Clarification: I want to distinguish between programs that run on any "x86" processor, and those that "require a 386", in a way that is simpler than setting up the two environments and attempting to run the program.

For real DOS programs, that is not windows programs, there is no simple to detect such, as DOS does simply not support 32 Bit. Any DOS program starts out as a 16 bit program, marked an MZ header. 32 Bit usage under DOS is custom made by the program itself and invisible to DOS. Of course, one could search for signatures of common DOS extenders, but that would still only cover part of all 32 Bit programs. Nor would it be decisive, as programs may include 32 bit support for memory access, but not being 32 bit themself.

Now, if this is about a 32 bit Windows EXE programs. Here, much like with DLL or any other file for Windows containing code, the file starts out with a PE-Header - which in turn starts out with a 16 bit EXE stub able to issue a warning (*1) if started under DOS.

Any detector looking for a (16 Bit) DOS program will find that EXE files as well as DLLs are exactly that, a DOS program.

After that stub (*2), the PE header contains several sections, foremost the COFF (Common Object File Format) chunk, which, after it's magic number, contains a machine type, telling what machine the code is intended for. Common values are

  • 00000h for 16 bit
  • 0014Ch for 32 Bit (Decimal 332 for 386-32)
  • 08664h for 64 bit

This is followed by more fields defining other characteristics, including word size and so on. For all practical purpose a machine type of 0014Ch is the most significant indicator for 32 Bit Windows applications. This is followed by an optional PE+ header, denoting different formats for headers in 64 bit versions.

Last but not least Windows DLL work exactly like Windows EXE featuring the same PE header.

While the PE format is nowadays the most common one, there are some variations. The most common are

  • New Executable (NE), introduced by Windows 1.0 / MSDOS, 16 bit
  • Linear Executable (LE), for mixed 16/32 bit in Windows 9x, OS2 and some DOS extenders
  • Linear Executables for OS2 2.0 (LX)
  • MP used with Phar Lap DOS extender.

*1 - The well known "This program cannot be run in DOS mode" message.

*2 - Consisting of the DOS header, a pointer to the PE header and the stub itself. Ignoring the pointer is a common error of programs trying to decode a PE format.

22
  • 7
    @TomasBy Windows programs do not have to have a GUI, Windows sppots as well CLI programs - still, they are not DOS programs, but Windows programs. They can use all Windows resources and functions. There is no way to distinguish a Windows CLI program form a Windows GUI program - well, no except missing runtime calls to the GUI. Bottom line, a Windows program not using the Windows GUI is not a DOS program, but stays a Windows program.
    – Raffzahn
    Feb 14 at 17:37
  • 2
    @TomasBy ‘Some are DOS, some are Win or OS/2. How do you quickly tell them apart’ – that’s a different question from what you have asked in your original post. Feb 14 at 17:51
  • 3
    You know the Windows command prompt (cmd.exe) is nothing to do with DOS, right? It just started life by implementing a very similar set of commands. FWIW, I spent a few years writing programs that ran on 32-bit Windows NT, which programs almost never had a GUI. Feb 14 at 21:41
  • 2
    Yeah, I don't know what that means to you. Suppose I type sc start foo at a cmd prompt. Is that "DOS-looking" even though what it's doing is sending an RPC to the service controller? Feb 14 at 22:55
  • 5
    @TomasBy - I'm sincerely attempting to help; but you have been using imprecise language, thus the "what do you mean by <I>X</I>?" questions. You may know what you mean, but we perhaps do not. Feb 15 at 0:40
1

Even within "16-bit mode", various 8086-compatible processors that have appeared over the years have extended the 8086 instruction set with instructions that weren't supported by earlier processors. Programs that only need to run on the later processors may exploit such instructions to perform various tasks more easily and efficiently than would otherwise have been possible. For example, on the 8086, if code wants to shift a 16-bit value right by five places, it would either have to load CL with 5 and then use the rotate-right-by-CL instruction, or else use the rotate-right-by-1 instruction five times. On later processors, however, one could simply use a rotate-right-by-immediate-value instruction with an immediate value of 5. Code which uses the latter instruction would be faster than code that uses the other approaches when run on later processors, but behave in meaningless fashion if run on an 8088 or 8086.

DOS has no clue about what particular instructions any particular program might use, and there is no standard way of marking programs to indicate that. While most programs that require an 80386 or later happen to make use of 32-bit registers, the only time an OS would need to care about that would be if it tries to multi-task two or more programs that both make use of 32-bit registers simultaneously. If two or more programs which use 32-bit registers are run simultaneously and a multi-tasker doesn't know how to save and restore such registers on a context switch, each program would be likely to trash register contents upon which the other would be relying. If only one program at a time uses 32-bit registers, however, this would be a non-issue. Whenever code switched away from that program, the system would save the bottom halves of 32-bit registers without saving the top halves, but since nothing else in the system would access the top halves of those registers, they would still hold the proper values once control returns to the only program that cares about them.

While it's possible for DOS programs to switch to 32-bit modes, many programs which make use of 32-bit registers do so without ever leaving "16-bit" mode, using instruction codes that had no assigned meaning on the 8086. Because DOS processes such programs just like any other, it neither knows nor cares about whether they use instructions that only have meaning on certain processors, and doesn't define any means by which programs can indicate what CPU is required.

1

For reference, I am posting the .txt and .ini files from the exetype program.

*********************************************************************
EXETYPE 1.0 is a tool to determine the nature of an executable file.
Syntax: EXETYPE <filename> 

NOTE:  EXETYPE does not yet support file wildcards.
    
    It is necessary to have the EXETYPE.INI file somewhere in your PATH 
    (if unsure, type PATH).  
    The format of EXETYPE.INI is as follows:
    
    [entry1]         
    TYPE    <string>
    ADDRESS <hex value> or **
    OFFSET  <hex value>
    MASK    <hex value>
    VALUE   <hex value> or !<hex value>
    ADDRESS <hex value> or **
    OFFSET  <hex value>
    MASK    <hex value>
    VALUE   <hex value> or !<hex value>
    .
    .
    .
    
    [entry2]         
    TYPE    <string>
    HEADER  <hex value> or **
    OFFSET  <hex value>
    MASK    <hex value>
    VALUE   <hex value> or !<hex value>
    .
    .
    .
    

    Each Entry has one TYPE field and from 1 to 5 groups of 
    ADDRESS, OFFSET, MASK and VALUE fields.      
    
    NOTE: All hex values must have an even number of characters (we're working 
      with byte values here.  If an odd number of nybbles is needed, please 
      pad with zeros. 
    
    A description of each entry follows:
     [entry] -  This denotes the start of a new entry.  80 characters maximum.
     TYPE        -  A description of the file type, 80 characters maximum.
     ADDRESS     -  Microsoft EXE files have the address of the header at a certain 
            location.  This value is kept at offset 3c from the beginning
            of the file.  If your entry does not use this, put in ** and it 
            will be ignored.
     OFFSET      -      This hex value denotes the offset from the beginning of the 
            header.  
     MASK        -      This bitmask lets you screen which bits of the bytes you want
            to look at.
     VALUE       -  This is the actual value you are comparing to the value of the
            bits at OFFSET strained through MASK.  If it is preceded by a "!", the 
            criterion is that the two values do NOT match.                     

The entries:

[DOS]
TYPE    DOS Executable 
ADDRESS **
OFFSET  00
MASK    FFFF
VALUE   4d5a 
ADDRESS 3c 
OFFSET  00
MASK    FFFF
VALUE   !4e45

[WIN]
TYPE    Windows Executable 
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45 
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   02

[OS/2]
TYPE    OS/2 Executable
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01

[OS2_Bound]
TYPE    Bound to run under DOS (or has a DOS stub)
ADDRESS  **
OFFSET  40
MASK    FF
VALUE   00
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01

[OS2_Real]
TYPE    Runs in real mode or protected mode
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01
ADDRESS 3c
OFFSET  0c
MASK    08
VALUE   00

[OS2_Protect]
TYPE    Runs only in protected mode
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01
ADDRESS 3c
OFFSET  0c
MASK    08
VALUE   08

[OS2_or_Win_DLL]
TYPE    Dynamic link library or driver
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  0d
MASK    80
VALUE   80

[OS2_Full]
TYPE    Full screen application
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01
ADDRESS 3c
OFFSET  0d
MASK    07
VALUE   01

[OS2_VIO]
TYPE    VIO application (can run in a window)
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01
ADDRESS 3c
OFFSET  0d
MASK    07
VALUE   02

[OS2_PM]
TYPE    Presentation Manager application
ADDRESS 3c
OFFSET  00
MASK    FFFF
VALUE   4e45
ADDRESS 3c
OFFSET  36
MASK    FF
VALUE   01
ADDRESS 3c
OFFSET  0d
MASK    07
VALUE   03

[NT]
TYPE    Windows NT
ADDRESS 3c 
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000
      
[NT_16bit]
TYPE    16 bit machine 
ADDRESS 3c
OFFSET  16
MASK    4001
VALUE   4000
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_32bit]
TYPE    32 bit machine
ADDRESS 3c
OFFSET  16
MASK    4001
VALUE   0001
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_DLL]
TYPE    Dynamic Link Library
ADDRESS 3c
OFFSET  16
MASK    0020
VALUE   0020
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_SYSTEM]
TYPE    System File
ADDRESS 3c
OFFSET  16
MASK    0010
VALUE   0010
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_i386]
TYPE    Built for the Intel 80386 processor
ADDRESS 3c
OFFSET  04
MASK    FFFF
VALUE   4c01
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_i486]
TYPE    Built for the Intel 80486 processor
ADDRESS 3c
OFFSET  04
MASK    FFFF
VALUE   4d01
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_MARK3]
TYPE    Built for the R4000 (MIPS) processor
ADDRESS 3c
OFFSET  04
MASK    FFFF
VALUE   6601
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_iPentium]
TYPE    Built for the Intel Pentium processor
ADDRESS 3c
OFFSET  04
MASK    FFFF
VALUE   4e01
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000
 
[NT_Alpha]
TYPE    Built for the DEC Alpha processor
ADDRESS 3c
OFFSET  04
MASK    FFFF
VALUE   8401
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000 

[NT_PowerPC]
TYPE    Built for the PowerPC processor
ADDRESS 3c
OFFSET  04
MASK    FFFF
VALUE   F001
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000 
 
[NT_NO_SUB]
TYPE    Requires no subsystem to run (Native to Windows NT)
ADDRESS 3c
OFFSET  5c 
MASK    0F
VALUE   01
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_WIN_GUI_SUB]
TYPE    Runs under the Windows GUI subsystem
ADDRESS 3c
OFFSET  5c  
MASK    0F
VALUE   02
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_WIN_CUI_SUB]
TYPE    Runs under the Windows character-based subsystem
ADDRESS 3c
OFFSET  5c  
MASK    0F
VALUE   03
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_OS2_SUB]
TYPE    Runs under the OS/2 subsystem
ADDRESS 3c
OFFSET  5c
MASK    0F
VALUE   05
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

[NT_POSIX_SUB]
TYPE    Runs under the POSIX subsystem
ADDRESS 3c
OFFSET  5c  
MASK    0F
VALUE   07
ADDRESS 3c
OFFSET  00
MASK    FFFFFFFF
VALUE   50450000

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.