home *** CD-ROM | disk | FTP | other *** search
/ For Beginners & Professional Hackers / cd.iso / docum / advdos.doc / s1c03 < prev    next >
Encoding:
Text File  |  1992-04-21  |  49.6 KB  |  964 lines

  1. ────────────────────────────────────────────────────────────────────────────
  2. Chapter 3  Structure of MS-DOS Application Programs
  3.  
  4.   Programs that run under MS-DOS come in two basic flavors: .COM programs,
  5.   which have a maximum size of approximately 64 KB, and .EXE programs, which
  6.   can be as large as available memory. In Intel 8086 parlance, .COM programs
  7.   fit the tiny model, in which all segment registers contain the same value;
  8.   that is, the code and data are mixed together. In contrast, .EXE programs
  9.   fit the small, medium, or large model, in which the segment registers
  10.   contain different values; that is, the code, data, and stack reside in
  11.   separate segments. .EXE programs can have multiple code and data segments,
  12.   which are respectively addressed by long calls and by manipulation of the
  13.   data segment (DS) register.
  14.  
  15.   A .COM-type program resides on the disk as an absolute memory image, in a
  16.   file with the extension .COM. The file does not have a header or any other
  17.   internal identifying information. A .EXE program, on the other hand,
  18.   resides on the disk in a special type of file with a unique header, a
  19.   relocation map, a checksum, and other information that is (or can be) used
  20.   by MS-DOS.
  21.  
  22.   Both .COM and .EXE programs are brought into memory for execution by the
  23.   same mechanism: the EXEC function, which constitutes the MS-DOS loader.
  24.   EXEC can be called with the filename of a program to be loaded by
  25.   COMMAND.COM (the normal MS-DOS command interpreter), by other shells or
  26.   user interfaces, or by another program that was previously loaded by EXEC.
  27.   If there is sufficient free memory in the transient program area, EXEC
  28.   allocates a block of memory to hold the new program, builds the program
  29.   segment prefix (PSP) at its base, and then reads the program into memory
  30.   immediately above the PSP. Finally, EXEC sets up the segment registers and
  31.   the stack and transfers control to the program.
  32.  
  33.   When it is invoked, EXEC can be given the addresses of additional
  34.   information, such as a command tail, file control blocks, and an
  35.   environment block; if supplied, this information will be passed on to the
  36.   new program. (The exact procedure for using the EXEC function in your own
  37.   programs is discussed, with examples, in Chapter 12.)
  38.  
  39.   .COM and .EXE programs are often referred to as transient programs. A
  40.   transient program "owns" the memory block it has been allocated and has
  41.   nearly total control of the system's resources while it is executing. When
  42.   the program terminates, either because it is aborted by the operating
  43.   system or because it has completed its work and systematically performed a
  44.   final exit back to MS-DOS, the memory block is then freed (hence the term
  45.   transient) and can be used by the next program in line to be loaded.
  46.  
  47.  
  48. The Program Segment Prefix
  49.  
  50.   A thorough understanding of the program segment prefix is vital to
  51.   successful programming under MS-DOS. It is a reserved area, 256 bytes
  52.   long, that is set up by MS-DOS at the base of the memory block allocated
  53.   to a transient program. The PSP contains some linkages to MS-DOS that can
  54.   be used by the transient program, some information MS-DOS saves for its
  55.   own purposes, and some information MS-DOS passes to the transient
  56.   program──to be used or not, as the program requires (Figure 3-1).
  57.  
  58.   Offset
  59.   0000H ┌────────────────────────────────────────────────────────┐
  60.         │                        Int 20H                         │
  61.   0002H ├────────────────────────────────────────────────────────┤
  62.         │            Segment, end of allocation block            │
  63.   0004H ├────────────────────────────────────────────────────────┤
  64.         │                        Reserved                        │
  65.   0005H ├────────────────────────────────────────────────────────┤
  66.         │        Long call to MS-DOS function dispatcher         │
  67.   000AH ├────────────────────────────────────────────────────────┤
  68.         │        Previous contents of termination handler        │
  69.         │               interrupt vector (Int 22H)               │
  70.   000EH ├────────────────────────────────────────────────────────┤
  71.         │ Previous contents of Ctrl-C interrupt vector (Int 23H) │
  72.   0012H ├────────────────────────────────────────────────────────┤
  73.         │      Previous contents of critical-error handler       │
  74.         │               interrupt vector (Int 24H)               │
  75.   0016H ├────────────────────────────────────────────────────────┤
  76.         │                        Reserved                        │
  77.   002CH ├────────────────────────────────────────────────────────┤
  78.         │          Segment address of environment block          │
  79.   002EH ├────────────────────────────────────────────────────────┤
  80.         │                        Reserved                        │
  81.   005CH ├────────────────────────────────────────────────────────┤
  82.         │             Default file control block #1              │
  83.   006CH ├────────────────────────────────────────────────────────┤
  84.         │             Default file control block #2              │
  85.         │              (overlaid if FCB #1 opened)               │
  86.   008OH ├────────────────────────────────────────────────────────┤
  87.         └──────────────────────────┐                             │
  88.         ┌────────────────────────┐ └─────────────────────────────┘
  89.         │                        └───────────────────────────────┐
  90.         │  Command tail and default disk transfer area (buffer)  │
  91.   OOFFH └────────────────────────────────────────────────────────┘
  92.  
  93.   Figure 3-1.  The structure of the program segment prefix.
  94.  
  95.   In the first versions of MS-DOS, the PSP was designed to be compatible
  96.   with a control area that was built beneath transient programs under
  97.   Digital Research's venerable CP/M operating system, so that programs could
  98.   be ported to MS-DOS without extensive logical changes. Although MS-DOS has
  99.   evolved considerably since those early days, the structure of the PSP is
  100.   still recognizably similar to its CP/M equivalent. For example, offset
  101.   0000H in the PSP contains a linkage to the MS-DOS process-termination
  102.   handler, which cleans up after the program has finished its job and
  103.   performs a final exit. Similarly, offset 0005H in the PSP contains a
  104.   linkage to the MS-DOS function dispatcher, which performs disk operations,
  105.   console input/output, and other such services at the request of the
  106.   transient program. Thus, calls to PSP:0000 and PSP:0005 have the same
  107.   effect as CALL 0000 and CALL 0005 under CP/M. (These linkages are not the
  108.   "approved" means of obtaining these services, however.)
  109.  
  110.   The word at offset 0002H in the PSP contains the segment address of the
  111.   top of the transient program's allocated memory block. The program can use
  112.   this value to determine whether it should request more memory to do its
  113.   job or whether it has extra memory that it can release for use by other
  114.   processes.
  115.  
  116.   Offsets 000AH through 0015H in the PSP contain the previous contents of
  117.   the interrupt vectors for the termination, Ctrl-C, and critical-error
  118.   handlers. If the transient program alters these vectors for its own
  119.   purposes, MS-DOS restores the original values saved in the PSP when the
  120.   program terminates.
  121.  
  122.   The word at PSP offset 002CH holds the segment address of the environment
  123.   block, which contains a series of ASCIIZ strings (sequences of ASCII
  124.   characters terminated by a null, or zero, byte). The environment block is
  125.   inherited from the program that called the EXEC function to load the
  126.   currently executing program. It contains such information as the current
  127.   search path used by COMMAND.COM to find executable programs, the location
  128.   on the disk of COMMAND.COM itself, and the format of the user prompt used
  129.   by COMMAND.COM.
  130.  
  131.   The command tail──the remainder of the command line that invoked the
  132.   transient program, after the program's name──is copied into the PSP
  133.   starting at offset 0081H. The length of the command tail, not including
  134.   the return character at its end, is placed in the byte at offset 0080H.
  135.   Redirection or piping parameters and their associated filenames do not
  136.   appear in the portion of the command line (the command tail) that is
  137.   passed to the transient program, because redirection is transparent to
  138.   applications.
  139.  
  140.   To provide compatibility with CP/M, MS-DOS parses the first two parameters
  141.   in the command tail into two default file control blocks (FCBs) at
  142.   PSP:005CH and PSP:006CH, under the assumption that they may be filenames.
  143.   However, if the parameters are filenames that include a path
  144.   specification, only the drive code will be valid in these default FCBs,
  145.   because FCB-type file- and record-access functions do not support
  146.   hierarchical file structures. Although the default FCBs were an aid in
  147.   earlier years, when compatibility with CP/M was more of a concern, they
  148.   are essentially useless in modern MS-DOS application programs that must
  149.   provide full path support. (File control blocks are discussed in detail in
  150.   Chapter 8 and hierarchical file structures are discussed in Chapter 9.)
  151.  
  152.   The 128-byte area from 0080H through 00FFH in the PSP also serves as the
  153.   default disk transfer area (DTA), which is set by MS-DOS before passing
  154.   control to the transient program. If the program does not explicitly
  155.   change the DTA, any file read or write operations requested with the FCB
  156.   group of function calls automatically use this area as a data buffer. This
  157.   is rarely useful and is another facet of MS-DOS's handling of the PSP that
  158.   is present only for compatibility with CP/M.
  159.  
  160.   ──────────────────────────────────────────────────────────────────────────
  161.   WARNING
  162.     Programs must not alter any part of the PSP below offset 005CH.
  163.   ──────────────────────────────────────────────────────────────────────────
  164.  
  165.  
  166. Introduction to .COM Programs
  167.  
  168.   Programs of the .COM persuasion are stored in disk files that hold an
  169.   absolute image of the machine instructions to be executed. Because the
  170.   files contain no relocation information, they are more compact, and are
  171.   loaded for execution slightly faster, than equivalent .EXE files. Note
  172.   that MS-DOS does not attempt to ascertain whether a .COM file actually
  173.   contains executable code (there is no signature or checksum, as in the
  174.   case of a .EXE file); it simply brings any file with the .COM extension
  175.   into memory and jumps to it.
  176.  
  177.   Because .COM programs are loaded immediately above the program segment
  178.   prefix and do not have a header that can specify another entry point, they
  179.   must always have an origin of 0100H, which is the length of the PSP.
  180.   Location 0100H must contain an executable instruction. The maximum length
  181.   of a .COM program is 65,536 bytes, minus the length of the PSP (256 bytes)
  182.   and a mandatory word of stack (2 bytes).
  183.  
  184.   When control is transferred to the .COM program from MS-DOS, all of the
  185.   segment registers point to the PSP (Figure 3-2). The stack pointer
  186.   register contains 0FFFEH if memory allows; otherwise, it is set as high as
  187.   possible in memory minus 2 bytes. (MS-DOS pushes a zero word on the stack
  188.   before entry.)
  189.  
  190.      SS:SP  ┌────────────────────────────────────────────────────────┐
  191.             │                                                        │
  192.             │       Stack grows downward from top of segment         │
  193.             │                           │                            │
  194.             │                                                       │
  195.             │                                                       │
  196.             │                           │                            │
  197.             │                 Program code and data                  │
  198.             │                                                        │
  199.   CS:0100H  ├────────────────────────────────────────────────────────┤
  200. A           │                 Program segment prefix                 │
  201.   CS:0000H  └────────────────────────────────────────────────────────┘
  202.   DS:0000H
  203.   ES:0000H
  204.   SS:0000H
  205.  
  206.   Figure 3-2.  A memory image of a typical .COM-type program after loading.
  207.   The contents of the .COM file are brought into memory just above the
  208.   program segment prefix. Program, code, and data are mixed together in the
  209.   same segment, and all segment registers contain the same value.
  210.  
  211.   Although the size of an executable .COM file can't exceed 64 KB, the
  212.   current versions of MS-DOS allocate all of the transient program area to
  213.   .COM programs when they are loaded. Because many such programs date from
  214.   the early days of MS-DOS and are not necessarily "well-behaved" in their
  215.   approach to memory management, the operating system simply makes the
  216.   worst-case assumption and gives .COM programs everything that is
  217.   available. If a .COM program wants to use the EXEC function to invoke
  218.   another process, it must first shrink down its memory allocation to the
  219.   minimum memory it needs in order to continue, taking care to protect its
  220.   stack. (This is discussed in more detail in Chapter 12.)
  221.  
  222.   When a .COM program finishes executing, it can return control to MS-DOS by
  223.   several means. The preferred method is Int 21H Function 4CH, which allows
  224.   the program to pass a return code back to the program, shell, or batch
  225.   file that invoked it. However, if the program is running under MS-DOS
  226.   version 1, it must exit by means of Int 20H, Int 21H Function 0, or a
  227.   NEAR RETURN. (Because a word of zero was pushed onto the stack at entry, a
  228.   NEAR RETURN causes a transfer to PSP:0000, which contains an Int 20H
  229.   instruction.)
  230.  
  231.   A .COM-type application can be linked together from many separate object
  232.   modules. All of the modules must use the same code-segment name and class
  233.   name, and the module with the entry point at offset 0100H within the
  234.   segment must be linked first. In addition, all of the procedures within a
  235.   .COM program should have the NEAR attribute, because all executable code
  236.   resides in one segment.
  237.  
  238.   When linking a .COM program, the linker will display the message
  239.  
  240.   Warning: no stack segment
  241.  
  242.   This message can be ignored. The linker output is a .EXE file, which must
  243.   be converted into a .COM file with the MS-DOS EXE2BIN utility before
  244.   execution. You can then delete the .EXE file. (An example of this process
  245.   is provided in Chapter 4.)
  246.  
  247. An Example .COM Program
  248.  
  249.   The HELLO.COM program listed in Figure 3-3 demonstrates the structure of
  250.   a simple assembly-language program that is destined to become a .COM file.
  251.   (You may find it helpful to compare this listing with the HELLO.EXE
  252.   program later in this chapter.) Because this program is so short and
  253.   simple, a relatively high proportion of the source code is actually
  254.   assembler directives that do not result in any executable code.
  255.  
  256.   The NAME statement simply provides a module name for use during the
  257.   linkage process. This aids understanding of the map that the linker
  258.   produces. In MASM versions 5.0 and later, the module name is always the
  259.   same as the filename, and the NAME statement is ignored.
  260.  
  261.   The PAGE command, when used with two operands, as in line 2, defines the
  262.   length and width of the page. These default respectively to 66 lines and
  263.   80 characters. If you use the PAGE command without any operands, a
  264.   formfeed is sent to the printer and a heading is printed. In larger
  265.   programs, use the PAGE command liberally to place each of your subroutines
  266.   on separate pages for easy reading.
  267.  
  268.   The TITLE command, in line 3, specifies the text string (limited to 60
  269.   characters) that is to be printed at the upper left corner of each page.
  270.   The TITLE command is optional and cannot be used more than once in each
  271.   assembly-language source file.
  272.  
  273.   ──────────────────────────────────────────────────────────────────────────
  274.    1:          name    hello
  275.    2:          page    55,132
  276.    3:          title   HELLO.COM--print hello on terminal
  277.    4:
  278.    5:  ;
  279.    6:  ; HELLO.COM:    demonstrates various components
  280.    7:  ;               of a functional .COM-type assembly-
  281.    8:  ;               language program, and an MS-DOS
  282.    9:  ;               function call.
  283.   10:  ;
  284.   11:  ; Ray Duncan, May 1988
  285.   12:  ;
  286.   13:
  287.   14:  stdin   equ     0               ; standard input handle
  288.   15:  stdout  equ     1               ; standard output handle
  289.   16:  stderr  equ     2               ; standard error handle
  290.   17:
  291.   18:  cr      equ     0dh             ; ASCII carriage return
  292.   19:  lf      equ     0ah             ; ASCII linefeed
  293.   20:
  294.   21:
  295.   22:  _TEXT   segment word public 'CODE'
  296.   23:
  297.   24:          org     100h            ; .COM files always have
  298.   25:                                  ; an origin of 100h
  299.   26:
  300.   27:          assume  cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT
  301.   28:
  302.   29:  print   proc    near            ; entry point from MS-DOS
  303.   30:
  304.   31:          mov     ah,40h          ; function 40h = write
  305.   32:          mov     bx,stdout       ; handle for standard output
  306.   33:          mov     cx,msg_len      ; length of message
  307.   34:          mov     dx,offset msg   ; address of message
  308.   35:          int     21h             ; transfer to MS-DOS
  309.   36:
  310.   37:          mov     ax,4c00h        ; exit, return code = 0
  311.   38:          int     21h             ; transfer to MS-DOS
  312.   39:
  313.   40:  print   endp
  314.   41:
  315.   42:
  316.   43:  msg     db      cr,lf           ; message to display
  317.   44:          db      'Hello World!',cr,lf
  318.   45:
  319.   46:  msg_len equ     $-msg           ; length of message
  320.   47:
  321.   48:
  322.   49:  _TEXT   ends
  323.   50:
  324.   51:          end     print           ; defines entry point
  325.   ──────────────────────────────────────────────────────────────────────────
  326.  
  327.   Figure 3-3.  The HELLO.COM program listing.
  328.  
  329.   Dropping down past a few comments and EQU statements, we come to a
  330.   declaration of a code segment that begins in line 22 with a SEGMENT
  331.   command and ends in line 49 with an ENDS command. The label in the
  332.   leftmost field of line 22 gives the code segment the name _TEXT. The
  333.   operand fields at the right end of the line give the segment the
  334.   attributes WORD, PUBLIC, and `CODE'. (You might find it helpful to read
  335.   the Microsoft Macro Assembler manual for detailed explanations of each
  336.   possible segment attribute.)
  337.  
  338.   Because this program is going to be converted into a .COM file, all of its
  339.   executable code and data areas must lie within one code segment. The
  340.   program must also have its origin at offset 0100H (immediately above the
  341.   program segment prefix), which is taken care of by the ORG statement
  342.   in line 24.
  343.  
  344.   Following the ORG instruction, we encounter an ASSUME statement on line
  345.   27. The concept of ASSUME often baffles new assembly-language programmers.
  346.   In a way, ASSUME doesn't "do" anything; it simply tells the assembler
  347.   which segment registers you are going to use to point to the various
  348.   segments of your program, so that the assembler can provide segment
  349.   overrides when they are necessary. It's important to notice that the
  350.   ASSUME statement doesn't take care of loading the segment registers with
  351.   the proper values; it merely notifies the assembler of your intent to do
  352.   that within the program. (Remember that, in the case of a .COM program,
  353.   MS-DOS initializes all the segment registers before entry to point to the
  354.   PSP.)
  355.  
  356.   Within the code segment, we come to another type of block declaration that
  357.   begins with the PROC command on line 29 and closes with ENDP on line 40.
  358.   These two instructions declare the beginning and end of a procedure, a
  359.   block of executable code that performs a single distinct function. The
  360.   label in the leftmost field of the PROC statement (in this case, print)
  361.   gives the procedure a name. The operand field gives it an attribute. If
  362.   the procedure carries the NEAR attribute, only other code in the same
  363.   segment can call it, whereas if it carries the FAR attribute, code located
  364.   anywhere in the CPU's memory-addressing space can call it. In .COM
  365.   programs, all procedures carry the NEAR attribute.
  366.  
  367.   For the purposes of this example program, I have kept the print procedure
  368.   ridiculously simple. It calls MS-DOS Int 21H Function 40H to send the
  369.   message Hello World! to the video screen, and calls Int 21H Function 4CH
  370.   to terminate the program.
  371.  
  372.   The END statement in line 51 tells the assembler that it has reached the
  373.   end of the source file and also specifies the entry point for the program.
  374.   If the entry point is not a label located at offset 0100H, the .EXE file
  375.   resulting from the assembly and linkage of this source program cannot be
  376.   converted into a .COM file.
  377.  
  378.  
  379. Introduction to .EXE Programs
  380.  
  381.   We have just discussed a program that was written in such a way that it
  382.   could be assembled into a .COM file. Such a program is simple in
  383.   structure, so a programmer who needs to put together this kind of quick
  384.   utility can concentrate on the program logic and do a minimum amount of
  385.   worrying about control of the assembler. However, .COM-type programs have
  386.   some definite disadvantages, and so most serious assembly-language efforts
  387.   for MS-DOS are written to be converted into .EXE files.
  388.  
  389.   Although .COM programs are effectively restricted to a total size of 64 KB
  390.   for machine code, data, and stack combined, .EXE programs can be
  391.   practically unlimited in size (up to the limit of the computer's available
  392.   memory). .EXE programs also place the code, data, and stack in separate
  393.   parts of the file. Although the normal MS-DOS program loader does not take
  394.   advantage of this feature of .EXE files, the ability to load different
  395.   parts of large programs into several separate memory fragments, as well as
  396.   the opportunity to designate a "pure" code portion of your program that
  397.   can be shared by several tasks, is very significant in multitasking
  398.   environments such as Microsoft Windows.
  399.  
  400.   The MS-DOS loader always brings a .EXE program into memory immediately
  401.   above the program segment prefix, although the order of the code, data,
  402.   and stack segments may vary (Figure 3-4). The .EXE file has a header, or
  403.   block of control information, with a characteristic format (Figures 3-5
  404.   and 3-6). The size of this header varies according to the number of
  405.   program instructions that need to be relocated at load time, but it is
  406.   always a multiple of 512 bytes.
  407.  
  408.   Before MS-DOS transfers control to the program, the initial values of the
  409.   code segment (CS) register and instruction pointer (IP) register are
  410.   calculated from the entry-point information in the .EXE file header and
  411.   the program's load address. This information derives from an END statement
  412.   in the source code for one of the program's modules. The data segment (DS)
  413.   and extra segment (ES) registers are made to point to the PSP so that the
  414.   program can access the environment-block pointer, command tail, and other
  415.   useful information contained there.
  416.  
  417.      SS:SP ┌────────────────────────────────────────────────────────┐
  418.            │                                                        │
  419.            │                     Stack segment:                     │
  420.            │        stack grows downward from top of segment        │
  421.            │                           │                            │
  422.            │                                                       │
  423.   SS:0000H ├────────────────────────────────────────────────────────┤
  424.            │                      Data segment                      │
  425.            ├────────────────────────────────────────────────────────┤
  426.            │                      Program code                      │
  427.   CS:0000H ├────────────────────────────────────────────────────────┤
  428.            │                 Program segment prefix                 │
  429.   DS:0000H └────────────────────────────────────────────────────────┘
  430.   ES:0000H
  431.  
  432.   Figure 3-4.  A memory image of a typical .EXE-type program immediately
  433.   after loading. The contents of the .EXE file are relocated and brought
  434.   into memory above the program segment prefix. Code, data, and stack reside
  435.   in separate segments and need not be in the order shown here. The entry
  436.   point can be anywhere in the code segment and is specified by the END
  437.   statement in the main module of the program. When the program receives
  438.   control, the DS (data segment) and ES (extra segment) registers point to
  439.   the program segment prefix; the program usually saves this value and then
  440.   resets the DS and ES registers to point to its data area.
  441.  
  442.   The initial contents of the stack segment (SS) and stack pointer (SP)
  443.   registers come from the header. This information derives from the
  444.   declaration of a segment with the attribute STACK somewhere in the
  445.   program's source code. The memory space allocated for the stack may be
  446.   initialized or uninitialized, depending on the stack-segment definition;
  447.   many programmers like to initialize the stack memory with a recognizable
  448.   data pattern so that they can inspect memory dumps and determine how much
  449.   stack space is actually used by the program.
  450.  
  451.   When a .EXE program finishes processing, it should return control to
  452.   MS-DOS through Int 21H Function 4CH. Other methods are available, but
  453.   they offer no advantages and are considerably less convenient (because
  454.   they usually require the CS register to point to the PSP).
  455.  
  456.   Byte
  457.   offset
  458.   0000H ┌────────────────────────────────────────────────────────┐
  459.         │           First of .EXE file signature (4DH)           │
  460.   0001H ├────────────────────────────────────────────────────────┤
  461.         │        Second part of .EXE file signature (5AH)        │
  462.   0002H ├────────────────────────────────────────────────────────┤
  463.         │                 Length of file MOD 512                 │
  464.   0004H ├────────────────────────────────────────────────────────┤
  465.         │    Size of file in 512-byte pages, including header    │
  466.   0006H ├────────────────────────────────────────────────────────┤
  467.         │            Number of relocation-table items            │
  468.   0008H ├────────────────────────────────────────────────────────┤
  469.         │      Size of header in paragraphs (16-byte units)      │
  470.   000AH ├────────────────────────────────────────────────────────┤
  471.         │   Minimum number of paragraphs needed above program    │
  472.   000CH ├────────────────────────────────────────────────────────┤
  473.         │   Maximum number of paragraphs desired above program   │
  474.   000EH ├────────────────────────────────────────────────────────┤
  475.         │          Segment displacement of stack module          │
  476.   0010H ├────────────────────────────────────────────────────────┤
  477.         │            Contents of SP register at entry            │
  478.   0012H ├────────────────────────────────────────────────────────┤
  479.         │                     Word checksum                      │
  480.   0014H ├────────────────────────────────────────────────────────┤
  481.         │            Contents of IP register at entry            │
  482.   0016H ├────────────────────────────────────────────────────────┤
  483.         │          Segment displacement of code module           │
  484.   0018H ├────────────────────────────────────────────────────────┤
  485.         │        Offset of first relocation item in file         │
  486.   001AH ├────────────────────────────────────────────────────────┤
  487.         │    Overlay number (0 for resident part of program)     │
  488.   001BH ├────────────────────────────────────────────────────────┤
  489.         │                Variable reserved space                 │
  490.         ├────────────────────────────────────────────────────────┤
  491.         │                    Relocation table                    │
  492.         ├────────────────────────────────────────────────────────┤
  493.         │                Variable reserved space                 │
  494.         ├────────────────────────────────────────────────────────┤
  495.         │               Program and data segments                │
  496.         ├────────────────────────────────────────────────────────┤
  497.         │                     Stack segment                      │
  498.         └────────────────────────────────────────────────────────┘
  499.  
  500.   Figure 3-5.  The format of a .EXE load module.
  501.  
  502.   The input to the linker for a .EXE-type program can be many separate
  503.   object modules. Each module can use a unique code-segment name, and the
  504.   procedures can carry either the NEAR or the FAR attribute, depending on
  505.   naming conventions and the size of the executable code. The programmer
  506.   must take care that the modules linked together contain only one segment
  507.   with the STACK attribute and only one entry point defined with an END
  508.   assembler directive. The output from the linker is a file with a .EXE
  509.   extension. This file can be executed immediately.
  510.  
  511.   ──────────────────────────────────────────────────────────────────────────
  512.   C>DUMP HELLO.EXE
  513.          0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
  514.   0000  4D 5A 28 00 02 00 01 00 20 00 09 00 FF FF 03 00  MZ(..... .......
  515.   0010  80 00 20 05 00 00 00 00 1E 00 00 00 01 00 01 00  .. .............
  516.   0020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  517.   0030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  518.   0040  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  519.   0050  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  520.         .
  521.         .
  522.         .
  523.   0200  B8 01 00 8E D8 B4 40 BB 01 00 B9 10 00 90 BA 08  ...............
  524.   0210  00 CD 21 B8 00 4C CD 21 0D 0A 48 65 6C 6C 6F 20  ..!..L.!..Hello
  525.   0220  57 6F 72 6C 64 21 0D 0A                          World!..
  526.   ──────────────────────────────────────────────────────────────────────────
  527.  
  528.   Figure 3-6.  A hex dump of the HELLO.EXE program, demonstrating the
  529.   contents of a simple .EXE load module. Note the following interesting
  530.   values: the .EXE signature in bytes 0000H and 0001H, the number of
  531.   relocation-table items in bytes 0006H and 0007H, the minimum extra memory
  532.   allocation (MIN_ALLOC) in bytes 000AH and 000BH, the maximum extra memory
  533.   allocation (MAX_ALLOC) in bytes 000CH and 000DH, and the initial IP
  534.   (instruction pointer) register value in bytes 0014H and 0015H. See also
  535.   Figure 3-5.
  536.  
  537. An Example .EXE Program
  538.  
  539.   The HELLO.EXE program in Figure 3-7 demonstrates the fundamental
  540.   structure of an assembly-language program that is destined to become a
  541.   .EXE file. At minimum, it should have a module name, a code segment, a
  542.   stack segment, and a primary procedure that receives control of the
  543.   computer from MS-DOS after the program is loaded. The HELLO.EXE program
  544.   also contains a data segment to provide a more complete example.
  545.  
  546.   The NAME, TITLE, and PAGE directives were covered in the HELLO.COM example
  547.   program and are used in the same manner here, so we'll move to the first
  548.   new item of interest. After a few comments and EQU statements, we come to
  549.   a declaration of a code segment that begins on line 21 with a SEGMENT
  550.   command and ends on line 41 with an ENDS command. As in the HELLO.COM
  551.   example program, the label in the leftmost field of the line gives the
  552.   code segment the name _TEXT. The operand fields at the right end of the
  553.   line give the attributes WORD, PUBLIC, and `CODE'.
  554.  
  555.   Following the code-segment instruction, we find an ASSUME statement on
  556.   line 23. Notice that, unlike the equivalent statement in the HELLO.COM
  557.   program, the ASSUME statement in this program specifies several different
  558.   segment names. Again, remember that this statement has no direct effect on
  559.   the contents of the segment registers but affects only the operation of
  560.   the assembler itself.
  561.  
  562.   ──────────────────────────────────────────────────────────────────────────
  563.    1:          name    hello
  564.    2:          page    55,132
  565.    3:          title   HELLO.EXE--print Hello on terminal
  566.    4:  ;
  567.    5:  ; HELLO.EXE:    demonstrates various components
  568.    6:  ;               of a functional .EXE-type assembly-
  569.    7:  ;               language program, use of segments,
  570.    8:  ;               and an MS-DOS function call.
  571.    9:  ;
  572.   10:  ; Ray Duncan, May 1988
  573.   11:  ;
  574.   12:
  575.   13:  stdin   equ     0               ; standard input handle
  576.   14:  stdout  equ     1               ; standard output handle
  577.   15:  stderr  equ     2               ; standard error handle
  578.   16:
  579.   17:  cr      equ     0dh             ; ASCII carriage return
  580.   18:  lf      equ     0ah             ; ASCII linefeed
  581.   19:
  582.   20:
  583.   21:  _TEXT   segment word public 'CODE'
  584.   22:
  585.   23:          assume  cs:_TEXT,ds:_DATA,ss:STACK
  586.   24:
  587.   25:  print   proc    far             ; entry point from MS-DOS
  588.   26:
  589.   27:          mov     ax,_DATA        ; make our data segment
  590.   28:          mov     ds,ax           ; addressable...
  591.   29:
  592.   30:          mov     ah,40h          ; function 40h = write
  593.   31:          mov     bx,stdout       ; standard output handle
  594.   32:          mov     cx,msg_len      ; length of message
  595.   33:          mov     dx,offset msg   ; address of message
  596.   34:          int     21h             ; transfer to MS-DOS
  597.   35:
  598.   36:          mov     ax,4c00h        ; exit, return code = 0
  599.   37:          int     21h             ; transfer to MS-DOS
  600.   38:
  601.   39:  print   endp
  602.   40:
  603.   41:  _TEXT   ends
  604.   42:
  605.   43:
  606.   44:  _DATA   segment word public 'DATA'
  607.   45:
  608.   46:  msg     db      cr,lf           ; message to display
  609.   47:          db      'Hello World!',cr,lf
  610.   48:
  611.   49:  msg_len equ     $-msg           ; length of message
  612.   50:
  613.   51:  _DATA   ends
  614.   52:
  615.   53:
  616.   54:  STACK   segment para stack `STACK'
  617.   55:
  618.   56:          db      128 dup (?)
  619.   57:
  620.   58:  STACK   ends
  621.   59:
  622.   60:          end     print           ; defines entry point
  623.   ──────────────────────────────────────────────────────────────────────────
  624.  
  625.   Figure 3-7.  The HELLO.EXE program listing.
  626.  
  627.   Within the code segment, the main print procedure is declared by the PROC
  628.   command on line 25 and closed with ENDP on line 39. Because the procedure
  629.   resides in a .EXE file, we have given it the FAR attribute as an example,
  630.   but the attribute is really irrelevant because the program is so small and
  631.   the procedure is not called by anything else in the same program.
  632.  
  633.   The print procedure first initializes the DS register, as indicated in the
  634.   earlier ASSUME statement, loading it with a value that causes it to point
  635.   to the base of the data area. (MS-DOS automatically sets up the CS and SS
  636.   registers.) Next, the procedure uses MS-DOS Int 21H Function 40H to
  637.   display the message Hello World! on the screen, just as in the HELLO.COM
  638.   program. Finally, the procedure exits back to MS-DOS with an Int 21H
  639.   Function 4CH on lines 36 and 37, passing a return code of zero (which by
  640.   convention means a success).
  641.  
  642.   Lines 44 through 51 declare a data segment named _DATA, which contains the
  643.   variables and constants the program will use. If the various modules of a
  644.   program contain multiple data segments with the same name, the linker will
  645.   collect them and place them in the same physical memory segment.
  646.  
  647.   Lines 54 through 58 establish a stack segment; PUSH and POP instructions
  648.   will access this area of scratch memory. Before MS-DOS transfers control
  649.   to a .EXE program, it sets up the SS and SP registers according to the
  650.   declared size and location of the stack segment. Be sure to allow enough
  651.   room for the maximum stack depth that can occur at runtime, plus a safe
  652.   number of extra words for registers pushed onto the stack during an MS-DOS
  653.   service call. If the stack overflows, it may damage your other code and
  654.   data segments and cause your program to behave strangely or even to crash
  655.   altogether!
  656.  
  657.   The END statement on line 60 winds up our brief HELLO.EXE program, telling
  658.   the assembler that it has reached the end of the source file and providing
  659.   the label of the program's point of entry from MS-DOS.
  660.  
  661.   The differences between .COM and .EXE programs are summarized in Figure
  662.   3-8.
  663.  
  664.  
  665.                      .COM program               .EXE program
  666.   ──────────────────────────────────────────────────────────────────────────
  667.   Maximum size       65,536 bytes minus 256     No limit
  668.                      bytes for PSP and 2 bytes
  669.                      for stack
  670.  
  671.   Entry point        PSP:0100H                  Defined by END statement
  672.  
  673.   AL at entry        00H if default FCB #1 has  Same
  674.                      valid drive, 0FFH if
  675.                      invalid drive
  676.  
  677.   AH at entry        00H if default FCB #2 has  Same
  678.                      valid drive, 0FFH if
  679.                      invalid drive
  680.  
  681.   CS at entry        PSP                        Segment containing module
  682.                                                 with entry point
  683.  
  684.   IP at entry        0100H                      Offset of entry point within
  685.                                                 its segment
  686.  
  687.   DS at entry        PSP                        PSP
  688.  
  689.   ES at entry        PSP                        PSP
  690.  
  691.   SS at entry        PSP                        Segment with STACK attribute
  692.  
  693.   SP at entry        0FFFEH or top word in      Size of segment defined with
  694.                      available memory,          STACK attribute
  695.                      whichever is lower
  696.  
  697.   Stack at entry     Zero word                  Initialized or uninitialized
  698.  
  699.   Stack size         65,536 bytes minus 256     Defined in segment with
  700.                      bytes for PSP and size of  STACK attribute
  701.                      executable code and data
  702.  
  703.   Subroutine calls   Usually NEAR               NEAR or FAR
  704.  
  705.   Exit method        Int 21H Function 4CH      Int 21H Function 4CH
  706.                      preferred, NEAR RET if     preferred
  707.                      MS-DOS version 1
  708.  
  709.   Size of file       Exact size of program      Size of program plus header
  710.                                                 (multiple of 512 bytes)
  711.   ──────────────────────────────────────────────────────────────────────────
  712.  
  713.  
  714.   Figure 3-8.  Summary of the differences between .COM and .EXE programs,
  715.   including their entry conditions.
  716.  
  717.  
  718. More About Assembly-Language Programs
  719.  
  720.   Now that we've looked at working examples of .COM and .EXE
  721.   assembly-language programs, let's backtrack and discuss their elements a
  722.   little more formally. The following discussion is based on the Microsoft
  723.   Macro Assembler, hereafter referred to as MASM. If you are familiar with
  724.   MASM and are an experienced assembly-language programmer, you may want to
  725.   skip this section.
  726.  
  727.   MASM programs can be thought of as having three structural levels:
  728.  
  729.   ■  The module level
  730.  
  731.   ■  The segment level
  732.  
  733.   ■  The procedure level
  734.  
  735.   Modules are simply chunks of source code that can be independently
  736.   maintained and assembled. Segments are physical groupings of like items
  737.   (machine code or data) within a program and a corresponding segregation of
  738.   dissimilar items. Procedures are functional subdivisions of an executable
  739.   program──routines that carry out a particular task.
  740.  
  741. Program Modules
  742.  
  743.   Under MS-DOS, the module-level structure consists of files containing the
  744.   source code for individual routines. Each source file is translated by the
  745.   assembler into a relocatable object module. An object module can reside
  746.   alone in an individual file or with many other object modules in an
  747.   object-module library of frequently used or related routines. The
  748.   Microsoft Object Linker (LINK) combines object-module files, often with
  749.   additional object modules extracted from libraries, into an executable
  750.   program file.
  751.  
  752.   Using modules and object-module libraries reduces the size of your
  753.   application source files (and vastly increases your productivity), because
  754.   these files need not contain the source code for routines they have in
  755.   common with other programs. This technique also allows you to maintain the
  756.   routines more easily, because you need to alter only one copy of their
  757.   source code stored in one place, instead of many copies stored in
  758.   different applications. When you improve (or fix) one of these routines,
  759.   you can simply reassemble it, put its object module back into the library,
  760.   relink all of the programs that use the routine, and voilga: instant
  761.   upgrade.
  762.  
  763. Program Segments
  764.  
  765.   The term segments refers to two discrete programming concepts: physical
  766.   segments and logical segments.
  767.  
  768.   Physical segments are 64 KB blocks of memory. The Intel 8086/8088 and
  769.   80286 microprocessors have four segment registers, which are essentially
  770.   used as pointers to these blocks. (The 80386 has six segment registers,
  771.   which are a superset of those found on the 8086/8088 and 80286.) Each
  772.   segment register can point to the bottom of a different 64 KB area of
  773.   memory. Thus, a program can address any location in memory by appropriate
  774.   manipulation of the segment registers, but the maximum amount of memory
  775.   that it can address simultaneously is 256 KB.
  776.  
  777.   As we discussed earlier in the chapter, .COM programs assume that all four
  778.   segment registers always point to the same place──the bottom of the
  779.   program. Thus, they are limited to a maximum size of 64 KB. .EXE programs,
  780.   on the other hand, can address many different physical segments and can
  781.   reset the segment registers to point to each segment as it is needed.
  782.   Consequently, the only practical limit on the size of a .EXE program is
  783.   the amount of available memory. The example programs throughout the
  784.   remainder of this book focus on .EXE programs.
  785.  
  786.   Logical segments are the program components. A minimum of three logical
  787.   segments must be declared in any .EXE program: a code segment, a data
  788.   segment, and a stack segment. Programs with more than 64 KB of code or
  789.   data have more than one code or data segment. The routines or data that
  790.   are used most frequently are put into the primary code and data segments
  791.   for speed, and routines or data that are used less frequently are put into
  792.   secondary code and data segments.
  793.  
  794.   Segments are declared with the SEGMENT and ENDS directives in the
  795.   following form:
  796.  
  797.   name   SEGMENT attributes
  798.   .
  799.   .
  800.   .
  801.   name   ENDS
  802.  
  803.   The attributes of a segment include its align type (BYTE, WORD, or PARA),
  804.   combine type (PUBLIC, PRIVATE, COMMON, or STACK), and class type. The
  805.   segment attributes are used by the linker when it is combining logical
  806.   segments to create the physical segments of an executable program. Most of
  807.   the time, you can get by just fine using a small selection of attributes
  808.   in a rather stereotypical way. However, if you want to use the full range
  809.   of attributes, you might want to read the detailed explanation in the MASM
  810.   manual.
  811.  
  812.   Programs are classified into one memory model or another based on the
  813.   number of their code and data segments. The most commonly used memory
  814.   model for assembly-language programs is the small model, which has one
  815.   code and one data segment, but you can also use the medium, compact, and
  816.   large models (Figure 3-9). (Two additional models exist with which we
  817.   will not be concerning ourselves further: the tiny model, which consists
  818.   of intermixed code and data in a single segment── for example, a .COM file
  819.   under MS-DOS; and the huge model, which is supported by the Microsoft C
  820.   Optimizing Compiler and which allows use of data structures larger than 64
  821.   KB.)
  822.  
  823.   Model                    Code segments           Data segments
  824.   ──────────────────────────────────────────────────────────────────────────
  825.   Small                    One                     One
  826.   Medium                   Multiple                One
  827.   Compact                  One                     Multiple
  828.   Large                    Multiple                Multiple
  829.   ──────────────────────────────────────────────────────────────────────────
  830.  
  831.   Figure 3-9.  Memory models commonly used in assembly-language and C
  832.   programs.
  833.  
  834.   For each memory model, Microsoft has established certain segment and class
  835.   names that are used by all its high-level-language compilers (Figure
  836.   3-10). Because segment names are arbitrary, you may as well adopt the
  837.   Microsoft conventions. Their use will make it easier for you to integrate
  838.   your assembly-language routines into programs written in languages such as
  839.   C, or to use routines from high-level-language libraries in your
  840.   assembly-language programs.
  841.  
  842.   Another important Microsoft high-level-language convention is to use the
  843.   GROUP directive to name the near data segment (the segment the program
  844.   expects to address with offsets from the DS register) and the stack
  845.   segment as members of DGROUP (the automatic data group), a special name
  846.   recognized by the linker and also by the program loaders in Microsoft
  847.   Windows and Microsoft OS/2. The GROUP directive causes logical segments
  848.   with different names to be combined into a single physical segment so that
  849.   they can be addressed using the same segment base address. In C programs,
  850.   DGROUP also contains the local heap, which is used by the C runtime
  851.   library for dynamic allocation of small amounts of memory.
  852.  
  853.  
  854.   Memory      Segment      Align       Combine     Class        Group
  855.   model       name         type        type        type
  856.   ──────────────────────────────────────────────────────────────────────────
  857.   Small       _TEXT        WORD        PUBLIC      CODE
  858.               _DATA        WORD        PUBLIC      DATA         DGROUP
  859.               STACK        PARA        STACK       STACK        DGROUP
  860.  
  861.   Medium      module_TEXT  WORD        PUBLIC      CODE
  862.               .            WORD        PUBLIC      DATA         DGROUP
  863.               .
  864.               .
  865.               _DATA
  866.               STACK        PARA        STACK       STACK        DGROUP
  867.  
  868.   Compact     _TEXT        WORD        PUBLIC      CODE
  869.               data         PARA        PRIVATE     FAR_DATA
  870.               .            WORD        PUBLIC      DATA         DGROUP
  871.               .
  872.               .
  873.               _DATA
  874.               STACK        PARA        STACK       STACK        DGROUP
  875.  
  876.   Large       module_TEXT  WORD        PUBLIC      CODE
  877.               .
  878.               .
  879.               .
  880.               data         PARA        PRIVATE     FAR_DATA
  881.               .
  882.               .
  883.               .
  884.               _DATA        WORD        PUBLIC      DATA         DGROUP
  885.               STACK        PARA        STACK       STACK        DGROUP
  886.   ──────────────────────────────────────────────────────────────────────────
  887.  
  888.  
  889.   Figure 3-10.  Segments, groups, and classes for the standard memory models
  890.   as used with assembly-language programs. The Microsoft C Optimizing
  891.   Compiler and other high-level-language compilers use a superset of these
  892.   segments and classes.
  893.  
  894.   For pure assembly-language programs that will run under MS-DOS, you can
  895.   ignore DGROUP. However, if you plan to integrate assembly-language
  896.   routines and programs written in high-level languages, you'll want to
  897.   follow the Microsoft DGROUP convention. For example, if you are planning
  898.   to link routines from a C library into an assembly-language program, you
  899.   should include the line
  900.  
  901.   DGROUP group _DATA,STACK
  902.  
  903.   near the beginning of the program.
  904.  
  905.   The final Microsoft convention of interest in creating .EXE programs is
  906.   segment order. The high-level compilers assume that code segments always
  907.   come first, followed by far data segments, followed by the near data
  908.   segment, with the stack and heap last. This order won't concern you much
  909.   until you begin integrating assembly-language code with routines from
  910.   high-level-language libraries, but it is easiest to learn to use the
  911.   convention right from the start.
  912.  
  913. Program Procedures
  914.  
  915.   The procedure level of program structure is partly real and partly
  916.   conceptual. Procedures are basically just a fancy guise for subroutines.
  917.  
  918.   Procedures within a program are declared with the PROC and ENDP directives
  919.   in the following form:
  920.  
  921.   name   PROC attribute
  922.   .
  923.   .
  924.   .
  925.          RET
  926.   name   ENDP
  927.  
  928.   The attribute carried by a PROC declaration, which is either NEAR or FAR,
  929.   tells the assembler what type of call you expect to use to enter the
  930.   procedure──that is, whether the procedure will be called from other
  931.   routines in the same segment or from routines in other segments. When the
  932.   assembler encounters a RET instruction within the procedure, it uses the
  933.   attribute information to generate the correct opcode for either a near
  934.   (intra-segment) or far (inter-segment) return.
  935.  
  936.   Each program should have a main procedure that receives control from
  937.   MS-DOS. You specify the entry point for the program by including the name
  938.   of the main procedure in the END statement in one of the program's source
  939.   files. The main procedure's attribute (NEAR or FAR) is really not too
  940.   important, because the program returns control to MS-DOS with a function
  941.   call rather than a RET instruction. However, by convention, most
  942.   programmers assign the main procedure the FAR attribute anyway.
  943.  
  944.   You should break the remainder of the program into procedures in an
  945.   orderly way, with each procedure performing a well-defined single
  946.   function, returning its results to its caller, and avoiding actions that
  947.   have global effects within the program. Ideally procedures invoke each
  948.   other only by CALL instructions, have only one entry point and one exit
  949.   point, and always exit by means of a RET instruction, never by jumping to
  950.   some other location within the program.
  951.  
  952.   For ease of understanding and maintenance, a procedure should not exceed
  953.   one page (about 60 lines); if it is longer than a page, it is probably too
  954.   complex and you should delegate some of its function to one or more
  955.   subsidiary procedures. You should preface the source code for each
  956.   procedure with a detailed comment that states the procedure's calling
  957.   sequence, results returned, registers affected, and any data items
  958.   accessed or modified. The effort invested in making your procedures
  959.   compact, clean, flexible, and well-documented will be repaid many times
  960.   over when you reuse the procedures in other programs.
  961.  
  962.  
  963.  
  964.