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

  1. ────────────────────────────────────────────────────────────────────────────
  2. Chapter 12  The EXEC Function
  3.  
  4.   The MS-DOS EXEC function (Int 21H Function 4BH) allows a program (called
  5.   the parent) to load any other program (called the child) from a storage
  6.   device, execute it, and then regain control when the child program is
  7.   finished.
  8.  
  9.   A parent program can pass information to the child in a command line, in
  10.   default file control blocks, and by means of a set of strings called the
  11.   environment block (discussed later in this chapter). All files or devices
  12.   that the parent opened using the handle file-management functions are
  13.   duplicated in the newly created child task; that is, the child inherits
  14.   all the active handles of the parent task. Any file operations on those
  15.   handles by the child, such as seeks or file I/O, also affect the file
  16.   pointers associated with the parent's handles.
  17.  
  18.   MS-DOS suspends execution of the parent program until the child program
  19.   terminates. When the child program finishes its work, it can pass an exit
  20.   code back to the parent, indicating whether it encountered any errors. It
  21.   can also, in turn, load other programs, and so on through many levels of
  22.   control, until the system runs out of memory.
  23.  
  24.   The MS-DOS command interpreter, COMMAND.COM, uses the EXEC function to run
  25.   its external commands and other application programs. Many popular
  26.   commercial programs, such as database managers and word processors, use
  27.   EXEC to run other programs (spelling checkers, for example) or to load a
  28.   second copy of COMMAND.COM, thereby allowing the user to list directories
  29.   or copy and rename files without closing all the application files and
  30.   stopping the main work in progress. EXEC can also be used to load program
  31.   overlay segments, although this use is uncommon.
  32.  
  33.  
  34. Making Memory Available
  35.  
  36.   In order for a parent program to use the EXEC function to load a child
  37.   program, sufficient unallocated memory must be available in the transient
  38.   program area.
  39.  
  40.   When the parent itself was loaded, MS-DOS allocated it a variable amount
  41.   of memory, depending upon its original file type──.COM or .EXE──and any
  42.   other information that was available to the loader. (See Chapter 11 for
  43.   further details.) Because the operating system has no foolproof way of
  44.   predicting how much memory any given program will require, it generally
  45.   allocates far more memory to a program than is really necessary.
  46.  
  47.   Therefore, a prospective parent program's first action should be to use
  48.   Int 21H Function 4AH (Resize Memory Block) to release any excess memory
  49.   allocation of its own to MS-DOS. In this case, the program should call Int
  50.   21H Function 4AH with the ES register pointing to the program segment
  51.   prefix of the program releasing memory and the BX register containing the
  52.   number of paragraphs of memory to retain for that program. (See Figure
  53.   11-1 for an example.)
  54.  
  55.   ──────────────────────────────────────────────────────────────────────────
  56.   WARNING
  57.     A .COM program must move its stack to a safe area if it is reducing its
  58.     memory allocation to less than 64 KB.
  59.   ──────────────────────────────────────────────────────────────────────────
  60.  
  61.  
  62. Requesting the EXEC Function
  63.  
  64.   To load and execute a child program, the parent must execute an Int 21H
  65.   with the registers set up as follows:
  66.  
  67.     AH = 4BH
  68.     AL = 00H (subfunction to load child program)
  69.     DS:DX = segment:offset of pathname for child program
  70.     ES:BX = segment:offset of parameter block
  71.  
  72.   The parameter block, in turn, contains addresses of other information
  73.   needed by the EXEC function.
  74.  
  75. The Program Name
  76.  
  77.   The name of the program to be run, which the calling program provides to
  78.   the EXEC function, must be an unambiguous file specification (no wildcard
  79.   characters) and must include an explicit .COM or .EXE extension. If the
  80.   path and disk drive are not supplied in the program name, MS-DOS uses the
  81.   current directory and default disk drive. (The sequential search for .COM,
  82.   .EXE, and .BAT files in all the locations listed in the PATH variable is
  83.   not a function of EXEC, but rather of the internal logic of COMMAND.COM.)
  84.  
  85.   You cannot EXEC a batch file directly; instead, you must EXEC a copy of
  86.   COMMAND.COM and pass the name of the batch file in the command tail, along
  87.   with the /C switch.
  88.  
  89. The Parameter Block
  90.  
  91.   The parameter block contains the addresses of four data objects:
  92.  
  93.   ■  The environment block
  94.  
  95.   ■  The command tail
  96.  
  97.   ■  Two default file control blocks
  98.  
  99.   The space reserved in the parameter block for the address of the
  100.   environment block is only 2 bytes and holds a segment address. The
  101.   remaining three addresses are all double-word addresses; that is, they are
  102.   4 bytes, with the offset in the first 2 bytes and the segment address in
  103.   the last 2 bytes.
  104.  
  105.   The Environment Block
  106.  
  107.   Each program that the EXEC function loads inherits a data structure called
  108.   an environment block from its parent. The pointer to the segment of the
  109.   block is at offset 002CH in the PSP. The environment block holds certain
  110.   information used by the system's command interpreter (usually COMMAND.COM)
  111.   and may also hold information to be used by transient programs. It has no
  112.   effect on the operation of the operating system proper.
  113.  
  114.   If the environment-block pointer in the EXEC parameter block contains
  115.   zero, the child program acquires a copy of the parent program's
  116.   environment block. Alternatively, the parent program can provide a segment
  117.   pointer to a different or expanded environment. The maximum size of the
  118.   environment block is 32 KB, so very large chunks of information can be
  119.   passed between programs by this mechanism.
  120.  
  121.   The environment block for any given program is static, implying that if
  122.   more than one generation of child programs is resident in RAM, each one
  123.   will have a distinct and separate copy of the environment block.
  124.   Furthermore, the environment block for a program that terminates and stays
  125.   resident is not updated by subsequent PATH and SET commands.
  126.  
  127.   You will find more details about the environment block later in this
  128.   chapter.
  129.  
  130.   The Command Tail
  131.  
  132.   MS-DOS copies the command tail into the child program's PSP at offset
  133.   0080H, as described in Chapter 3. The information takes the form of a
  134.   count byte, followed by a string of ASCII characters, terminated by a
  135.   carriage return; the carriage return is not included in the count.
  136.  
  137.   The command tail can include filenames, switches, or other parameters.
  138.   From the child program's point of view, the command tail should provide
  139.   the same information that would be present if the program had been run by
  140.   a direct user command at the MS-DOS prompt. EXEC ignores any
  141.   I/O-redirection parameters placed in the command tail; the parent program
  142.   must provide for redirection of the standard devices before the EXEC
  143.   call is made.
  144.  
  145.   The Default File Control Blocks
  146.  
  147.   MS-DOS copies the two default file control blocks pointed to by the EXEC
  148.   parameter block into the child program's PSP at offsets 005CH and 006CH.
  149.   To emulate the function of COMMAND.COM from the child program's point of
  150.   view, the parent program should use Int 21H Function 29H (the system
  151.   parse-filename service) to parse the first two parameters of the command
  152.   tail into the default file control blocks before invoking the EXEC
  153.   function.
  154.  
  155.   File control blocks are not much use under MS-DOS versions 2 and 3,
  156.   because they do not support the hierarchical file structure, but some
  157.   application programs do inspect them as a quick way to get at the first
  158.   two switches or other parameters in the command tail. Chapter 8 discusses
  159.   file control blocks in more detail.
  160.  
  161.  
  162. Returning from the EXEC Function
  163.  
  164.   In MS-DOS version 2, the EXEC function destroys the contents of all
  165.   registers except the code segment (CS) and instruction pointer (IP).
  166.   Therefore, before making the EXEC call, the parent program must push the
  167.   contents of any other registers that are important onto the stack and then
  168.   save the stack segment (SS) and stack pointer (SP) registers in variables.
  169.   Upon return from a successful EXEC call (that is, the child program has
  170.   finished executing), the parent program should reload SS and SP from the
  171.   variables where they were saved and then pop the other saved registers off
  172.   the stack. In MS-DOS versions 3.0 and later, the stack and other registers
  173.   are preserved across the EXEC call in the usual fashion.
  174.  
  175.   Finally, the parent can use Int 21H Function 4DH to obtain the
  176.   termination type and return code of the child program.
  177.  
  178.   The EXEC function will fail under the following conditions:
  179.  
  180.   ■  Not enough unallocated memory is available to load and execute the
  181.      requested program file.
  182.  
  183.   ■  The requested program can't be found on the disk.
  184.  
  185.   ■  The transient portion of COMMAND.COM in highest RAM (which contains the
  186.      actual loader) has been destroyed and not enough free memory is
  187.      available to reload it (PC-DOS version 2 only).
  188.  
  189.   Figure 12-1 summarizes the calling convention for function 4BH. Figure
  190.   12-2 shows a skeleton of a typical EXEC call. This particular example
  191.   uses the EXEC function to load and run the MS-DOS utility CHKDSK.COM. The
  192.   SHELL.ASM program listing later in this chapter (Figure 12-5) presents a
  193.   more complete example that includes the use of Int 21H Function 4AH to
  194.   free unneeded memory.
  195.  
  196.   ──────────────────────────────────────────────────────────────────────────
  197.  
  198.   Called with:
  199.  
  200.     AH           = 4BH
  201.     AL           = function type
  202.                    00 = load and execute program
  203.                    03 = load overlay
  204.     ES:BX        = segment:offset of parameter block
  205.     DS:DX        = segment:offset of program specification
  206.  
  207.   Returns:
  208.  
  209.   If call succeeded
  210.  
  211.   Carry flag clear. In MS-DOS version 2, all registers except for CS:IP may
  212.   be destroyed. In MS-DOS versions 3.0 and later, registers are preserved in
  213.   the usual fashion.
  214.  
  215.   If call failed
  216.  
  217.   Carry flag set and AX = error code.
  218.  
  219.   Parameter block format:
  220.  
  221.   If AL = 0 (load and execute program)
  222.  
  223.     Bytes 0─1          = segment pointer, environment block
  224.     Bytes 2─3          = offset of command-line tail
  225.     Bytes 4─5          = segment of command-line tail
  226.     Bytes 6─7          = offset of first file control block to be copied
  227.                          into new PSP + 5CH
  228.     Bytes 8─9          = segment of first file control block
  229.     Bytes 10─11        = offset of second file control block to be copied
  230.                          into new PSP + 6CH
  231.     Bytes 12─13        = segment of second file control block
  232.  
  233.   If AL = 3 (load overlay)
  234.  
  235.     Bytes 0─1    = segment address where file will be loaded
  236.     Bytes 2─3    = relocation factor to apply to loaded image
  237.  
  238.   ──────────────────────────────────────────────────────────────────────────
  239.  
  240.   Figure 12-1.  Calling convention for the EXEC function (Int 21H Function
  241.   4BH).
  242.  
  243.   ──────────────────────────────────────────────────────────────────────────
  244.   cr      egu     0dh             ; ASCII carriage return
  245.           .
  246.           .
  247.           .
  248.           mov     stkseg,ss       ; save stack pointer
  249.           mov     stkptr,sp
  250.  
  251.           mov     dx,offset pname ; DS:DX = program name
  252.           mov     bx,offset pars  ; ES:BX = param block
  253.           mov     ax,4b00h        ; function 4bh, subfunction 00h
  254.           int     21h             ; transfer to MS-DOS
  255.  
  256.           mov     ax,_DATA        ; make our data segment
  257.           mov     ds,ax           ; addressable again
  258.           mov     es,ax
  259.  
  260.           cli                     ; (for bug in some 8088s)
  261.           mov     ss,stkseg       ; restore stack pointer
  262.           mov     sp,stkptr
  263.           sti                     ; (for bug in some 8088s)
  264.  
  265.           jc      error           ; jump if EXEC failed
  266.           .
  267.           .
  268.           .
  269.  
  270.   stkseg  dw      0               ; original SS contents
  271.   stkptr  dw      0               ; original SP contents
  272.  
  273.   pname   db      '\CHKDSK.COM',0 ; pathname of child program
  274.  
  275.   pars    dw      envir           ; environment segment
  276.           dd      cmdline         ; command line for child
  277.           dd      fcb1            ; file control block #1
  278.           dd      fcb2            ; file control block #2
  279.  
  280.   cmdline db      4,' *.*',cr     ; command line for child
  281.  
  282.   fcb1    db      0               ; file control block #1
  283.           db      11 dup ('?')
  284.           db      25 dup (0)
  285.   fcb2    db      0               ; file control block #2
  286.           db      11 dup (' ')
  287.           db      25 dup (0)
  288.  
  289.  
  290.   envir   segment para 'ENVIR'    ; environment segment
  291.  
  292.           db      'PATH=',0       ; empty search path
  293.                                   ; location of COMMAND.COM
  294.           db      'COMSPEC=A:\COMMAND.COM',0
  295.           db      0               ; end of environment
  296.  
  297.   envir   ends
  298.   ──────────────────────────────────────────────────────────────────────────
  299.  
  300.   Figure 12-2.  A brief example of the use of the MS-DOS EXEC call, with all
  301.   necessary variables and command blocks. Note the protection of the
  302.   registers for MS-DOS version 2 and the masking of interrupts during
  303.   loading of SS:SP to circumvent a bug in some early 8088 CPUs.
  304.  
  305.  
  306. More About the Environment Block
  307.  
  308.   The environment block is always paragraph aligned (starts at an address
  309.   that is a multiple of 16 bytes) and contains a series of ASCIIZ strings.
  310.   Each of the strings takes the following form:
  311.  
  312.     NAME=PARAMETER
  313.  
  314.   An additional zero byte (Figure 12-3) indicates the end of the entire set
  315.   of strings. Under MS-DOS version 3, the block of environment strings and
  316.   the extra zero byte are followed by a word count and the complete drive,
  317.   path, filename, and extension used by EXEC to load the program.
  318.  
  319.   ──────────────────────────────────────────────────────────────────────────
  320.         0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 0123456789ABCDEF
  321.   0000 43 4F 4D 53 50 45 43 3D 43 3A 5C 43 4F 4D 4D 41 COMSPEC=C:\COMMA
  322.   0010 4E 44 2E 43 4F 4D 00 50 52 4F 4D 50 54 3D 24 70 NDcom.PROMPT=$p
  323.   0020 24 5F 24 64 20 20 20 24 74 24 68 24 68 24 68 24 $_$d   $t$h$h$h$
  324.   0030 68 24 68 24 68 20 24 71 24 71 24 67 00 50 41 54 h$h$h $q$q$g.PAT
  325.   0040 48 3D 43 3A 5C 53 59 53 54 45 4D 3B 43 3A 5C 41 H=C:\SYSTEM;C:\A
  326.   0050 53 4D 3B 43 3A 5C 57 53 3B 43 3A 5C 45 54 48 45 SM;C:\WS;C:\ETHE
  327.   0060 52 4E 45 54 3B 43 3A 5C 46 4F 52 54 48 5C 50 43 RNET;C:\FORTH\PC
  328.   0070 33 31 3B 00 00 01 00 43 3A 5C 46 4F 52 54 48 5C 31;....C:\FORTH\
  329.   0080 50 43 33 31 5C 46 4F 52 54 48 2E 43 4F 4D 00 20 PC31\FORTH.COM.
  330.   ──────────────────────────────────────────────────────────────────────────
  331.  
  332.   Figure 12-3.  Dump of a typical environment block under MS-DOS version 3.
  333.   This particular example contains the default COMSPEC parameter and two
  334.   relatively complex PATH and PROMPT control strings that were set up by
  335.   entries in the user's AUTOEXEC file. Note the path and file specification
  336.   of the executing program following the double zeros at offset 0073H that
  337.   denote the end of the environment block.
  338.  
  339.   Under normal conditions, the environment block inherited by a program will
  340.   contain at least three strings:
  341.  
  342.     COMSPEC=variable
  343.     PATH=variable
  344.     PROMPT=variable
  345.  
  346.   MS-DOS places these three strings into the environment block at system
  347.   initialization, during the interpretation of SHELL, PATH, and PROMPT
  348.   directives in the CONFIG.SYS and AUTOEXEC.BAT files. The strings tell the
  349.   MS-DOS command interpreter, COMMAND.COM, the location of its executable
  350.   file (to enable it to reload the transient portion), where to search for
  351.   executable external commands or program files, and the format of the user
  352.   prompt.
  353.  
  354.   You can add other strings to the environment block, either interactively
  355.   or in batch files, with the SET command. Transient programs can use these
  356.   strings for informational purposes. For example, the Microsoft C Compiler
  357.   looks in the environment block for INCLUDE, LIB, and TMP strings to tell
  358.   it where to find its #include files and library files and where to build
  359.   its temporary working files.
  360.  
  361.  
  362. Example Programs: SHELL.C and SHELL.ASM
  363.  
  364.   As a practical example of use of the MS-DOS EXEC function, I have included
  365.   a small command interpreter called SHELL, with equivalent Microsoft C
  366.   (Figure 12-4) and Microsoft Macro Assembler (Figure 12-5) source code.
  367.   The source code for the assembly-language version is considerably more
  368.   complex than the code for the C version, but the names and functionality
  369.   of the various procedures are quite parallel.
  370.  
  371.   ──────────────────────────────────────────────────────────────────────────
  372.   /*
  373.       SHELL.C     Simple extendable command interpreter
  374.                   for MS-DOS versions 2.0 and later
  375.  
  376.       Copyright 1988 Ray Duncan
  377.  
  378.       Compile:    C>CL SHELL.C
  379.  
  380.       Usage:      C>SHELL
  381.   */
  382.   #include <stdio.h>
  383.   #include <process.h>
  384.   #include <stdlib.h>
  385.   #include <signal.h>
  386.  
  387.                                       /* macro to return number of
  388.                                          elements in a structure  */
  389.   #define dim(x) (sizeof(x) / sizeof(x[0]))
  390.  
  391.   unsigned intrinsic(char *);         /* function prototypes      */
  392.   void extrinsic(char *);
  393.   void get_cmd(char *);
  394.   void get_comspec(char *);
  395.   void break_handler(void);
  396.   void cls_cmd(void);
  397.   void dos_cmd(void);
  398.   void exit_cmd(void);
  399.  
  400.   struct cmd_table {                  /* intrinsic commands table */
  401.                      char *cmd_name;
  402.                      int  (*cmd_fxn)();
  403.                    }   commands[] =
  404.  
  405.                    { "CLS",   cls_cmd,
  406.                      "DOS",   dos_cmd,
  407.                      "EXIT",  exit_cmd, };
  408.  
  409.   static char com_spec[64];           /* COMMAND.COM filespec     */
  410.  
  411.   main(int argc, char *argv[])
  412.   {
  413.       char inp_buf[80];               /* keyboard input buffer    */
  414.  
  415.       get_comspec(com_spec);          /* get COMMAND.COM filespec */
  416.  
  417.                                       /* register new handler
  418.                                          for Ctrl-C interrupts    */
  419.       if(signal(SIGINT, break_handler) == (int(*)()) -1)
  420.       {
  421.           fputs("Can't capture Control-C Interrupt", stderr);
  422.           exit(1);
  423.       }
  424.  
  425.       while(1)                        /* main interpreter loop    */
  426.       {
  427.           get_cmd(inp_buf);           /* get a command            */
  428.           if (! intrinsic(inp_buf) )  /* if it's intrinsic,
  429.                                          run its subroutine       */
  430.              extrinsic(inp_buf);      /* else pass to COMMAND.COM */
  431.           }
  432.   }
  433.  
  434.  
  435.   /*
  436.       Try to match user's command with intrinsic command
  437.       table. If a match is found, run the associated routine
  438.       and return true; else return false.
  439.   */
  440.  
  441.   unsigned intrinsic(char *input_string)
  442.   {
  443.       int i, j;                       /* some scratch variables   */
  444.  
  445.                                       /* scan off leading blanks  */
  446.       while(*input_string == '\x20') input_string++ ;
  447.  
  448.                                       /* search command table     */
  449.       for(i=0; i < dim(commands); i++)
  450.       {
  451.           j = strcmp(commands[i].cmd_name, input_string);
  452.  
  453.           if(j == 0)                  /* if match, run routine    */
  454.           {
  455.               (*commands[i].cmd_fxn)();
  456.               return(1);              /* and return true          */
  457.           }
  458.       }
  459.       return(0);                      /* no match, return false   */
  460.   }
  461.  
  462.  
  463.   /*
  464.       Process an extrinsic command by passing it
  465.       to an EXEC'd copy of COMMAND.COM.
  466.   */
  467.  
  468.   void extrinsic(char *input_string)
  469.   {
  470.       int status;
  471.       status = system(input_string);      /* call EXEC function   */
  472.  
  473.       if(status)                          /* if failed, display
  474.                                              error message        */
  475.           fputs("\nEXEC of COMMAND.COM failed\n", stderr);
  476.   }
  477.  
  478.  
  479.   /*
  480.       Issue prompt, get user's command from standard input,
  481.       fold it to uppercase.
  482.   */
  483.  
  484.   void get_cmd(char *buffer)
  485.   {
  486.       printf("\nsh: ");                   /* display prompt       */
  487.       gets(buffer);                       /* get keyboard entry   */
  488.       strupr(buffer);                     /* fold to uppercase    */
  489.   }
  490.  
  491.  
  492.   /*
  493.       Get the full path and file specification for COMMAND.COM
  494.       from the COMSPEC variable in the environment.
  495.   */
  496.  
  497.   void get_comspec(char *buffer)
  498.   {
  499.       strcpy(buffer, getenv("COMSPEC"));
  500.  
  501.       if(buffer[0] == NULL)
  502.       {
  503.           fputs("\nNo COMSPEC in environment\n", stderr);
  504.           exit(1);
  505.       }
  506.   }
  507.  
  508.  
  509.   /*
  510.       This Ctrl-C handler keeps SHELL from losing control.
  511.       It just reissues the prompt and returns.
  512.   */
  513.   void break_handler(void)
  514.   {
  515.       signal(SIGINT, break_handler);      /* reset handler        */
  516.       printf("\nsh: ");                   /* display prompt       */
  517.   }
  518.  
  519.  
  520.   /*
  521.       These are the subroutines for the intrinsic commands.
  522.   */
  523.  
  524.   void cls_cmd(void)                      /* CLS command          */
  525.   {
  526.       printf("\033[2J");                  /* ANSI escape sequence */
  527.   }                                       /* to clear screen      */
  528.  
  529.   void dos_cmd(void)                      /* DOS command          */
  530.   {
  531.       int status;
  532.                                           /* run COMMAND.COM      */
  533.       status = spawnlp(P_WAIT, com_spec, com_spec, NULL);
  534.  
  535.       if (status)
  536.           fputs("\nEXEC of COMMAND.COM failed\n",stderr);
  537.   }
  538.  
  539.   void exit_cmd(void)                     /* EXIT command         */
  540.   {
  541.       exit(0);                            /* terminate SHELL      */
  542.   }
  543.   ──────────────────────────────────────────────────────────────────────────
  544.  
  545.   Figure 12-4.  SHELL.C: A table-driven command interpreter written in
  546.   Microsoft C.
  547.  
  548.   ──────────────────────────────────────────────────────────────────────────
  549.           name    shell
  550.           page    55,132
  551.           title   SHELL.ASM--simple MS-DOS shell
  552.   ;
  553.   ; SHELL.ASM     Simple extendable command interpreter
  554.   ;               for MS-DOS versions 2.0 and later
  555.   ;
  556.   ; Copyright 1988 by Ray Duncan
  557.   ;
  558.   ; Build:        C>MASM SHELL;
  559.   ;               C>LINK SHELL;
  560.   ;
  561.   ; Usage:        C>SHELL;
  562.   ;
  563.  
  564.   stdin   equ     0                       ; standard input handle
  565.   stdout  equ     1                       ; standard output handle
  566.   stderr  equ     2                       ; standard error handle
  567.  
  568.   cr      equ     0dh                     ; ASCII carriage return
  569.   lf      equ     0ah                     ; ASCII linefeed
  570.   blank   equ     20h                     ; ASCII blank code
  571.   escape  equ     01bh                    ; ASCII escape code
  572.  
  573.   _TEXT   segment word public 'CODE'
  574.  
  575.           assume  cs:_TEXT,ds:_DATA,ss:STACK
  576.  
  577.   shell   proc    far                     ; at entry DS = ES = PSP
  578.  
  579.           mov     ax,_DATA                ; make our data segment
  580.           mov     ds,ax                   ; addressable
  581.  
  582.           mov     ax,es:[002ch]           ; get environment segment
  583.           mov     env_seg,ax              ; from PSP and save it
  584.  
  585.                                           ; release unneeded memory...
  586.                                           ; ES already = PSP segment
  587.           mov     bx,100h                 ; BX = paragraphs needed
  588.           mov     ah,4ah                  ; function 4ah = resize block
  589.           int     21h                     ; transfer to MS-DOS
  590.           jnc     shell1                  ; jump if resize OK
  591.  
  592.           mov     dx,offset msg1          ; resize failed, display
  593.           mov     cx,msg1_length          ; error message and exit
  594.           jmp     shell4
  595.  
  596.   shell1: call    get_comspec             ; get COMMAND.COM filespec
  597.           jnc     shell2                  ; jump if it was found
  598.  
  599.           mov     dx,offset msg3          ; COMSPEC not found in
  600.           mov     cx,msg3_length          ; environment, display error
  601.           jmp     shell4                  ; message and exit
  602.   shell2: mov     dx,offset shell3        ; set Ctrl-C vector (int 23h)
  603.           mov     ax,cs                   ; for this program's handler
  604.           mov     ds,ax                   ; DS:DX = handler address
  605.           mov     ax,2523h                ; function 25h = set vector
  606.        n  int     21h                     ; transfer to MS-DOS
  607.  
  608.           mov     ax,_DATA                ; make our data segment
  609.           mov     ds,ax                   ; addressable again
  610.           mov     es,ax
  611.  
  612.   shell3:                                 ; main interpreter loop
  613.  
  614.           call    get_cmd                 ; get a command from user
  615.  
  616.           call    intrinsic               ; check if intrinsic function
  617.           jnc     shell3                  ; yes, it was processed
  618.  
  619.           call    extrinsic               ; no, pass it to COMMAND.COM
  620.           jmp     shell3                  ; then get another command
  621.  
  622.   shell4:                                 ; come here if error detected
  623.                                           ; DS:DX = message address
  624.                                           ; CX = message length
  625.           mov     bx,stderr               ; BX = standard error handle
  626.           mov     ah,40h                  ; function 40h = write
  627.           int     21h                     ; transfer to MS-DOS
  628.  
  629.           mov     ax,4c01h                ; function 4ch = terminate with
  630.                                           ; return code = 1
  631.           int     21h                     ; transfer to MS-DOS
  632.  
  633.   shell   endp
  634.  
  635.  
  636.  
  637.   intrinsic proc  near                    ; decode user entry against
  638.                                           ; the table "COMMANDS"
  639.                                           ; if match, run the routine,
  640.                                           ; and return carry = false
  641.                                           ; if no match, carry = true
  642.                                           ; return carry = true
  643.  
  644.           mov     si,offset commands      ; DS:SI = command table
  645.  
  646.   intr1:  cmp     byte ptr [si],0         ; end of table?
  647.           je      intr7                   ; jump, end of table found
  648.           mov     di,offset inp_buf       ; no, let DI = addr of user input
  649.  
  650.   intr2:  cmp     byte ptr [di],blank     ; scan off any leading blanks
  651.           jne     intr3
  652.  
  653.           inc     di                      ; found blank, go past it
  654.           jmp     intr2
  655.  
  656.   intr3:  mov     al,[si]                 ; next character from table
  657.  
  658.           or      al,al                   ; end of string?
  659.           jz      intr4                   ; jump, entire string matched
  660.  
  661.           cmp     al,[di]                 ; compare to input character
  662.           jnz     intr6                   ; jump, found mismatch
  663.  
  664.           inc     si                      ; advance string pointers
  665.           inc     di
  666.           jmp     intr3
  667.  
  668.   intr4:  cmp     byte ptr [di],cr        ; be sure user's entry
  669.           je      intr5                   ; is the same length...
  670.           cmp     byte ptr [di],blank     ; next character in entry
  671.           jne     intr6                   ; must be blank or return
  672.  
  673.   intr5:  call    word ptr [si+1]         ; run the command routine
  674.  
  675.           clc                             ; return carry flag = false
  676.           ret                             ; as success flag
  677.  
  678.   intr6:  lodsb                           ; look for end of this
  679.           or      al,al                   ; command string (null byte)
  680.           jnz     intr6                   ; not end yet, loop
  681.  
  682.           add     si,2                    ; skip over routine address
  683.           jmp     intr1                   ; try to match next command
  684.  
  685.   intr7:  stc                             ; command not matched, exit
  686.           ret                             ; with carry = true
  687.  
  688.   intrinsic endp
  689.   extrinsic proc  near                    ; process extrinsic command
  690.                                           ; by passing it to
  691.                                           ; COMMAND.COM with a
  692.                                           ; " /C " command tail
  693.  
  694.           mov     al,cr                   ; find length of command
  695.           mov     cx,cmd_tail_length      ; by scanning for carriage
  696.           mov     di,offset cmd_tail+1    ; return
  697.           cld
  698.           repnz scasb
  699.  
  700.           mov     ax,di                   ; calculate command-tail
  701.           sub     ax,offset cmd_tail+2    ; length without carriage
  702.           mov     cmd_tail,al             ; return, and store it
  703.  
  704.                                           ; set command-tail address
  705.           mov     word ptr par_cmd,offset cmd_tail
  706.           call    exec                    ; and run COMMAND.COM
  707.           ret
  708.  
  709.   extrinsic endp
  710.  
  711.  
  712.   get_cmd proc    near                    ; prompt user, get command
  713.  
  714.                                           ; display the shell prompt
  715.           mov     dx,offset prompt        ; DS:DX = message address
  716.           mov     cx,prompt_length        ; CX = message length
  717.           mov     bx,stdout               ; BX = standard output handle
  718.           mov     ah,40h                  ; function 40h = write
  719.           int     21h                     ; transfer to MS-DOS
  720.  
  721.                                           ; get entry from user
  722.           mov     dx,offset inp_buf       ; DS:DX = input buffer
  723.           mov     cx,inp_buf_length       ; CX = max length to read
  724.           mov     bx,stdin                ; BX = standard input handle
  725.           mov     ah,3fh                  ; function 3fh = read
  726.           int     21h                     ; transfer to MS-DOS
  727.  
  728.           mov     si,offset inp_buf       ; fold lowercase characters
  729.           mov     cx,inp_buf_length       ; in entry to uppercase
  730.   gcmd1:  cmp     byte ptr [si],'a'       ; check if 'a-z'
  731.           jb      gcmd2                   ; jump, not in range
  732.           cmp     byte ptr [si],'z'       ; check if 'a-z'
  733.           ja      gcmd2                   ; jump, not in range
  734.           sub     byte ptr [si],'a'-'A'   ; convert to uppercase
  735.  
  736.   gcmd2:  inc     si                      ; advance through entry
  737.           loop    gcmd1
  738.           ret                             ; back to caller
  739.  
  740.   get_cmd endp
  741.  
  742.  
  743.  
  744.   get_comspec proc near                   ; get location of COMMAND.COM
  745.                                           ; from environment "COMSPEC="
  746.                                           ; returns carry = false
  747.                                           ; if COMSPEC found
  748.                                           ; returns carry = true
  749.                                           ; if no COMSPEC
  750.  
  751.           mov     si,offset com_var       ; DS:SI = string to match...
  752.           call    get_env                 ; search environment block
  753.           jc      gcsp2                   ; jump if COMSPEC not found
  754.  
  755.                                           ; ES:DI points past "="
  756.           mov     si,offset com_spec      ; DS:SI = local buffer
  757.  
  758.   gcsp1:  mov     al,es:[di]              ; copy COMSPEC variable
  759.           mov     [si],al                 ; to local buffer
  760.           inc     si
  761.           inc     di
  762.           or      al,al                   ; null char? (turns off carry)
  763.           jnz     gcsp1                   ; no, get next character
  764.  
  765.   gcsp2:  ret                             ; back to caller
  766.  
  767.   get_comspec endp
  768.  
  769.  
  770.   get_env proc    near                    ; search environment
  771.                                           ; call DS:SI = "NAME="
  772.                                           ; uses contents of "ENV_SEG"
  773.                                           ; returns carry = false and ES:DI
  774.                                           ; pointing to parameter if found,
  775.                                           ; returns carry = true if no match
  776.           mov     es,env_seg              ; get environment segment
  777.           xor     di,di                   ; initialize env offset
  778.  
  779.   genv1:  mov     bx,si                   ; initialize pointer to name
  780.           cmp     byte ptr es:[di],0      ; end of environment?
  781.           jne     genv2                   ; jump, end not found
  782.  
  783.           stc                             ; no match, return carry set
  784.           ret
  785.  
  786.   genv2:  mov     al,[bx]                 ; get character from name
  787.           or      al,al                   ; end of name? (turns off carry)
  788.           jz      genv3                   ; yes, name matched
  789.  
  790.           cmp     al,es:[di]              ; compare to environment
  791.           jne     genv4                   ; jump if match failed
  792.  
  793.           inc     bx                      ; advance environment
  794.           inc     di                      ; and name pointers
  795.           jmp     genv2
  796.  
  797.   genv3:                                  ; match found, carry = clear,
  798.           ret                             ; ES:DI = variable
  799.  
  800.   genv4:  xor     al,al                   ; scan forward in environment
  801.           mov     cx,-1                   ; for zero byte
  802.           cld
  803.           repnz   scasb
  804.           jmp     genv1                   ; go compare next string
  805.  
  806.   get_env endp
  807.  
  808.  
  809.   exec    proc    near                    ; call MS-DOS EXEC function
  810.                                           ; to run COMMAND.COM
  811.  
  812.           mov     stkseg,ss               ; save stack pointer
  813.           mov     stkptr,sp
  814.  
  815.                                           ; now run COMMAND.COM
  816.           mov     dx,offset com_spec      ; DS:DX = filename
  817.           mov     bx,offset par_blk       ; ES:BX = parameter block
  818.           mov     ax,4b00h                ; function 4bh = EXEC
  819.                                           ; subfunction 0 =
  820.                                           ; load and execute
  821.           int     21h                     ; transfer to MS-DOS
  822.  
  823.           mov     ax,_DATA                ; make data segment
  824.           mov     ds,ax                   ; addressable again
  825.           mov     es,ax
  826.  
  827.           cli                             ; (for bug in some 8088s)
  828.           mov     ss,stkseg               ; restore stack pointer
  829.           mov     sp,stkptr
  830.           sti                             ; (for bug in some 8088s)
  831.  
  832.           jnc     exec1                   ; jump if no errors
  833.  
  834.                                           ; display error message
  835.           mov     dx,offset msg2          ; DS:DX = message address
  836.           mov     cx,msg2_length          ; CX = message length
  837.           mov     bx,stderr               ; BX = standard error handle
  838.           mov     ah,40h                  ; function 40h = write
  839.           int     21h                     ; transfer to MS-DOS
  840.  
  841.   exec1:  ret                             ; back to caller
  842.  
  843.   exec    endp
  844.  
  845.  
  846.  
  847.   cls_cmd proc    near                    ; intrinsic CLS command
  848.  
  849.           mov     dx,offset cls_str       ; send the ANSI escape
  850.           mov     cx,cls_str_length       ; sequence to clear
  851.           mov     bx,stdout               ; the screen
  852.           mov     ah,40h
  853.           int     21h
  854.           ret
  855.  
  856.   cls_cmd endp
  857.  
  858.  
  859.   dos_cmd proc    near                    ; intrinsic DOS command
  860.  
  861.                                           ; set null command tail
  862.           mov     word ptr par_cmd,offset nultail
  863.           call    exec                    ; and run COMMAND.COM
  864.           ret
  865.  
  866.   dos_cmd endp
  867.   exit_cmd proc   near                    ; intrinsic EXIT command
  868.  
  869.           mov     ax,4c00h                ; call MS-DOS terminate
  870.           int     21h                     ; function with
  871.                                           ; return code of zero
  872.   exit_cmd endp
  873.  
  874.   _TEXT   ends
  875.  
  876.  
  877.   STACK   segment para stack 'STACK'      ; declare stack segment
  878.  
  879.           dw      64 dup (?)
  880.  
  881.   STACK   ends
  882.  
  883.   _DATA   segment word public 'DATA'
  884.  
  885.   commands equ $                          ; "intrinsic" commands table
  886.                                           ; each entry is ASCIIZ string
  887.                                           ; followed by the offset
  888.                                           ; of the procedure to be
  889.                                           ; executed for that command
  890.           db      'CLS',0
  891.           dw      cls_cmd
  892.  
  893.           db      'DOS',0
  894.           dw      dos_cmd
  895.  
  896.           db      'EXIT',0
  897.           dw      exit_cmd
  898.  
  899.           db      0                       ; end of table
  900.  
  901.   com_var db      'COMSPEC=',0            ; environment variable
  902.  
  903.                                           ; COMMAND.COM filespec
  904.   com_spec db     80 dup (0)              ; from environment COMSPEC=
  905.  
  906.   nultail db      0,cr                    ; null command tail for
  907.                                           ; invoking COMMAND.COM
  908.                                           ; as another shell
  909.  
  910.   cmd_tail db     0,' /C '                ; command tail for invoking
  911.                                           ; COMMAND.COM as a transient
  912.   inp_buf db      80 dup (0)              ; command line from standard input
  913.  
  914.   inp_buf_length equ $-inp_buf
  915.   cmd_tail_length equ $-cmd_tail-1
  916.  
  917.   prompt  db      cr,lf,'sh: '            ; SHELL's user prompt
  918.   prompt_length equ $-prompt
  919.  
  920.   env_seg dw      0                       ; segment of environment block
  921.  
  922.   msg1    db      cr,lf
  923.           db      'Unable to release memory.'
  924.           db      cr,lf
  925.   msg1_length equ $-msg1
  926.  
  927.   msg2    db      cr,lf
  928.           db      'EXEC of COMMAND.COM failed.'
  929.           db      cr,lf
  930.   msg2_length equ $-msg2
  931.  
  932.   msg3    db      cr,lf
  933.           db      'No COMSPEC variable in environment.'
  934.           db      cr,lf
  935.   msg3_length equ $-msg3
  936.  
  937.   cls_str db      escape,'[2J'            ; ANSI escape sequence
  938.   cls_str_length equ $-cls_str            ; to clear the screen
  939.  
  940.                                           ; EXEC parameter block
  941.   par_blk dw      0                       ; environment segment
  942.   par_cmd dd      cmd_tail                ; command line
  943.           dd      fcb1                    ; file control block #1
  944.           dd      fcb2                    ; file control block #2
  945.  
  946.   fcb1    db      0                       ; file control block #1
  947.           db      11 dup (' ')
  948.           db      25 dup (0)
  949.  
  950.   fcb2    db      0                       ; file control block #2
  951.           db      11 dup (' ')
  952.           db      25 dup (0)
  953.  
  954.   stkseg  dw      0                       ; original SS contents
  955.   stkptr  dw      0                       ; original SP contents
  956.  
  957.   _DATA   ends
  958.  
  959.           end     shell
  960.   ──────────────────────────────────────────────────────────────────────────
  961.  
  962.   Figure 12-5.  SHELL.ASM: A simple table-driven command interpreter written
  963.   in Microsoft Macro Assembler.
  964.  
  965.   The SHELL program is table driven and can easily be extended to provide a
  966.   powerful customized user interface for almost any application. When SHELL
  967.   takes control of the system, it displays the prompt
  968.  
  969.   sh:
  970.  
  971.   and waits for input from the user. After the user types a line terminated
  972.   by a carriage return, SHELL tries to match the first token in the line
  973.   against its table of internal (intrinsic) commands. If it finds a match,
  974.   it calls the appropriate subroutine. If it does not find a match, it calls
  975.   the MS-DOS EXEC function and passes the user's input to COMMAND.COM with
  976.   the /C switch, essentially using COMMAND.COM as a transient command
  977.   processor under its own control.
  978.  
  979.   As supplied in these listings, SHELL "knows" exactly three internal
  980.   commands:
  981.  
  982.   Command            Action
  983.   ──────────────────────────────────────────────────────────────────────────
  984.   CLS                Uses the ANSI standard control sequence to clear the
  985.                      display screen and home the cursor.
  986.   DOS                Runs a copy of COMMAND.COM.
  987.   EXIT               Exits SHELL, returning control of the system to the
  988.                      next lower command interpreter.
  989.   ──────────────────────────────────────────────────────────────────────────
  990.  
  991.   You can quickly add new intrinsic commands to either the C version or the
  992.   assembly-language version of SHELL. Simply code a procedure with the
  993.   appropriate action and insert the name of that procedure, along with the
  994.   text string that defines the command, into the table COMMANDS. In
  995.   addition, you can easily prevent SHELL from passing certain "dangerous"
  996.   commands (such as MKDIR or ERASE) to COMMAND.COM simply by putting the
  997.   names of the commands to be screened out into the intrinsic command table
  998.   with the address of a subroutine that prints an error message.
  999.  
  1000.   To summarize, the basic flow of both versions of the SHELL program is
  1001.   as follows:
  1002.  
  1003.   1.  The program calls MS-DOS Int 21H Function 4AH (Resize Memory Block)
  1004.       to shrink its memory allocation, so that the maximum possible space
  1005.       will be available for COMMAND.COM if it is run as an overlay. (This is
  1006.       explicit in the assembly-language version only. To keep the example
  1007.       code simple, the number of paragraphs to be reserved is coded as a
  1008.       generous literal value, rather than being figured out at runtime from
  1009.       the size and location of the various program segments.)
  1010.  
  1011.   2.  The program searches the environment for the COMSPEC variable, which
  1012.       defines the location of an executable copy of COMMAND.COM. If it can't
  1013.       find the COMSPEC variable, it prints an error message and exits.
  1014.  
  1015.   3.  The program puts the address of its own handler in the Ctrl-C vector
  1016.       (Int 23H) so that it won't lose control if the user enters a Ctrl-C
  1017.       or a Ctrl-Break.
  1018.  
  1019.   4.  The program issues a prompt to the standard output device.
  1020.  
  1021.   5.  The program reads a buffered line from the standard input device to
  1022.       get the user's command.
  1023.  
  1024.   6.  The program matches the first blank-delimited token in the line
  1025.       against its table of intrinsic commands. If it finds a match, it
  1026.       executes the associated procedure.
  1027.  
  1028.   7.  If the program does not find a match in the table of intrinsic
  1029.       commands, it synthesizes a command-line tail by appending the user's
  1030.       input to the /C switch and then EXECs a copy of COMMAND.COM, passing
  1031.       the address of the synthesized command tail in the EXEC parameter
  1032.       block.
  1033.  
  1034.   8.  The program repeats steps 4 through 7 until the user enters the
  1035.       command EXIT, which is one of the intrinsic commands, and which causes
  1036.       SHELL to terminate execution.
  1037.  
  1038.   In its present form, SHELL allows COMMAND.COM to inherit a full copy of
  1039.   the current environment. However, in some applications it may be helpful,
  1040.   or safer, to pass a modified copy of the environment block so that the
  1041.   secondary copy of COMMAND.COM will not have access to certain information.
  1042.  
  1043.  
  1044. Using EXEC to Load Overlays
  1045.  
  1046.   Loading overlays with the EXEC function is much less complex than using
  1047.   EXEC to run another program. The overlay can be constructed as either a
  1048.   memory image (.COM) or relocatable (.EXE) file and need not be the same
  1049.   type as the program that loads it. The main program, called the root
  1050.   segment, must carry out the following steps to load and execute an
  1051.   overlay:
  1052.  
  1053.   1.  Make a memory block available to receive the overlay. The program that
  1054.       calls EXEC must own the memory block for the overlay.
  1055.  
  1056.   2.  Set up the overlay parameter block to be passed to the EXEC function.
  1057.       This block contains the segment address of the block that will receive
  1058.       the overlay, plus a segment relocation value to be applied to the
  1059.       contents of the overlay file (if it is a .EXE file). These are
  1060.       normally the same value.
  1061.  
  1062.   3.  Call the MS-DOS EXEC function to load the overlay by issuing an Int
  1063.       21H with the registers set up as follows:
  1064.  
  1065.       AH = 4BH
  1066.       AL = 03H (EXEC subfunction to load overlay)
  1067.       DS:DX = segment:offset of overlay file pathname
  1068.       ES:BX = segment:offset of overlay parameter block
  1069.  
  1070.       Upon return from the EXEC function, the carry flag is clear if the
  1071.       overlay was found and loaded. The carry flag is set if the file could
  1072.       not be found or if some other error occurred.
  1073.  
  1074.   4.  Execute the code within the overlay by transferring to it with a far
  1075.       call. The overlay should be designed so that either the entry point or
  1076.       a pointer to the entry point is at the beginning of the module after
  1077.       it is loaded. This technique allows you to maintain the root and
  1078.       overlay modules separately, because the root module does not contain
  1079.       any "magical" knowledge of addresses within the overlay segment.
  1080.  
  1081.   To prevent users from inadvertently running an overlay directly from the
  1082.   command line, you should assign overlay files an extension other than .COM
  1083.   or .EXE. It is most convenient to relate overlays to their root segment by
  1084.   assigning them the same filename but a different extension, such as .OVL
  1085.   or .OV1, .OV2, and so on.
  1086.  
  1087.   Figure 12-6 shows the use of EXEC to load and execute an overlay.
  1088.  
  1089.   ──────────────────────────────────────────────────────────────────────────
  1090.           .
  1091.           .
  1092.           .
  1093.                                   ; allocate memory for overlay
  1094.           mov     bx,1000h        ; get 64 KB (4096 paragraphs)
  1095.           mov     ah,48h          ; function 48h = allocate block
  1096.           int     21h             ; transfer to MS-DOS
  1097.           jc      error           ; jump if allocation failed
  1098.  
  1099.           mov     pars,ax         ; set load address for overlay
  1100.           mov     pars+2,ax       ; set relocation segment for overlay
  1101.  
  1102.                                   ; set segment of entry point
  1103.           mov     word ptr entry+2,ax
  1104.  
  1105.           mov     stkseg,ss       ; save root's stack pointer
  1106.           mov     stkptr,sp
  1107.  
  1108.           mov     ax,ds           ; set ES = DS
  1109.           mov     es,ax
  1110.  
  1111.           mov     dx,offset oname ; DS:DX = overlay pathname
  1112.           mov     bx,offset pars  ; ES:BX = parameter block
  1113.           mov     ax,4b03h        ; function 4bh, subfunction 03h
  1114.           int     21h             ; transfer to MS-DOS
  1115.  
  1116.           mov     ax,_DATA        ; make our data segment
  1117.           mov     ds,ax           ; addressable again
  1118.           mov     es,ax
  1119.  
  1120.           cli                     ; (for bug in some early 8088s)
  1121.           mov     ss,stkseg       ; restore stack pointer
  1122.           mov     sp,stkptr
  1123.           sti                     ; (for bug in some early 8088s)
  1124.  
  1125.           jc      error           ; jump if EXEC failed
  1126.  
  1127.                                   ; otherwise EXEC succeeded...
  1128.           push    ds              ; save our data segment
  1129.           call    dword ptr entry ; now call the overlay
  1130.           pop     ds              ; restore our data segment
  1131.           .
  1132.           .
  1133.           .
  1134.  
  1135.   oname   db      'OVERLAY.OVL',0 ; pathname of overlay file
  1136.  
  1137.   pars    dw      0               ; load address (segment) for file
  1138.           dw      0               ; relocation (segment) for file
  1139.  
  1140.   entry   dd      0               ; entry point for overlay
  1141.  
  1142.   stkseg  dw      0               ; save SS register
  1143.   stkptr  dw      0               ; save SP register
  1144.   ──────────────────────────────────────────────────────────────────────────
  1145.  
  1146.   Figure 12-6.  A code skeleton for loading and executing an overlay with
  1147.   the EXEC function. The overlay file may be in either .COM or .EXE format.
  1148.  
  1149.  
  1150.  
  1151.