Project    Document last modified: 

Overview

Computer operating systems provide management and access services for the hardware resources of the system. These services are often accessible at the programmer level as callable functions or at the user level through some interface such as a command line in DOS and Unix or a GUI as in Mac or Windows.. In DOS one types dir and the command.com program reads the command and executes the directory listing program or function. In Windows one clicks the Windows Explorer icon to perform much the same operation.

The project is to implement a server that gives users access to operating system functions and programs over the Internet through a standard browser. Much of the functionality of a Web server will be implemented, including the ability to execute CGI (Common Gateway Interface) programs. Commands or programs can be entered from a browser, sent to the server, executed, and the program output displayed back to the browser. From a browser, it will be possible to control a remote computer's operation over an Internet connection. The remote computer or server will execute C++, Assembly programs and many DOS commands from a browser, providing access and control of  the server computer from across the Internet. In general, the operation of the server is somewhat similar to the TicTacToe server of a previous homework assignment.

A minimal server has already been implemented in a combination of C++ and Assembler as a starting point. The main project tasks are to: 

  1. translate several of the existing C++ functions into Assembler, 
  2. extend the server to perform additional functions, and 
  3. implement some useful CGI operations as Assembler programs.

The following diagram illustrates how the browser would get a directory listing of the server computer. The user typed  dir into the browser, which sends the request to the server, which executes the DOS dir command. The dir command prints the directory listing to standard output, which is sent back to the server, and finally back to the browser, where it is displayed.

The following three figures depict:
  1. User input of dir *.cpp to display the directory of only the C++ files.
  2. The directory results displayed by the browser.
  3. The server trace window showing the input to the server from the browser and the output from the dir execution that was sent o the browser.
User input of dir *.cpp to display the directory of only the C++ files.

The directory results displayed by the browser.

The server trace window showing the input to the server from the browser 
and the output from the dir execution that was sent o the browser.

What the Server Can Do

The server lets one control the computer on which the server runs from a browser. It provides very basic access to the computer on which it is running by executing the commands typed into the browser. The server recognizes a limited number of commands that are implemented internal to the server program as regular C++ or Assembler functions. Any command not recognized internally is passed on to DOS which attempts to execute the command as a program contained in the server directory or as a DOS command. Essentially any well behaved program that can be executed from the DOS command line can also be executed by the server, that includes 16-bit and 32-bit programs.

The definition of a well behaved program is one that uses standard input and standard output. This was discussed in Chapter 9 notes in more detail. The basic idea is that a program that reads standard input and writes standard output can read the keyboard or a file, write the screen or a file, without any changes to the program. This is performed by the operating system through redirection. For example, the following C++ program reads until a ' ' or blank is entered, converting characters 'A'-'Z' to lowercase and outputting (note that other characters might be mangled).

Suppose the name of the program executable is DOSlow.exe. Once compiled, the program can be executed several ways.

  1. DOSlow - The input is from the keyboard and output to the screen.
  2. DOSlow < A2Z - The input is from the file A2Z and output to the screen.
  3. DOSlow > z2a - The input is from the keyboard and output to the file z2a.
  4. DOSlow < A2Z > z2a - The input is from the file A2Z and output to the file z2a. Assuming that the A2Z file contained the one line:

  5.                     ABCDE Z 12345
    the program would output to the file z2a the characters through the first ' ' converted to lowercase as:
                        abcde
  6. DOSlow ABCDE Z 12345 - From the browser as in the second figure below. The third figure is the resulting output, stopping at the first ' '.
Convert to Lowercase on Standard Input and Output
C++ and Assembler
 
#include <iostream.h>
void main(void) {
    char c;

    do {
       cin >> c;
       c = c | 0x20;
       cout << c;
    } while (c != ' ');
}







.model small

.code

Main	Proc	near

@@do:				
	Mov	Ah, 8	 ; cin >> Al	
	Int	21H		
	Push	Ax	

	Or	Al, 20h
	Mov	Dl, Al
	Mov	Ah, 2     ; cout << Dl
	Int	21H
	
	Pop	Ax
@@while:Cmp	Al, ' '
	Jne	@@do
	
	Mov	Ah, 4ch
	Int	21H
Main	Endp
	End	Main
Input to Remote Server
Remote Server results

How the Server Works

The server and browser interact as follows for implementing the Help command:
  1. The user types in a command to the browser, such as Help, and presses the Enter key to send the string Help to the server. The browser can be on any computer on the Internet using the server name such as www.ius.edu. If the browser and server are on the same machine, the server is known as localhost.
  2. The server receives the string and determines whether it can execute the command Help. Help is one of the internal commands implemented by the server, which executes the command by returning HTML text for the help.
  3. The browser receives the HTML text and renders it following the HTML instructions.

Browser sends user input "Help" to server. Server recognizes "Help" as an internal server command.

Server responds by sending HTML text for the "Help" command.
The server, as illustrated above, gets its input from the browser. This input is just the string that the person using the browser typed in before hitting the Enter key. The server examines this input to determine whether either an internal command, one that the server can handle itself, or an external command, one that the server doesn't have a clue about and passes on.  The server examines the input from the browser and determines whether the first word the user typed was a server command. Basically, the command tells the server what internal server function the user wants executed. If the first word is an internal server command, the server just executes the appropriate C++ or Assembler function. In the above figure the input is a server command help which is handled internally by the server executing the HELP function and sending back a string of help stuff.

If the server doesn't recognize that first word as a command, the server lets DOS have a go at executing that word as the name of a DOS program. In the below diagrams, the dir command is unrecognized by the server so is passed on to DOS which returns a string containing the server directory listing. For the dir command:

  1. The user types in a command to the browser, such as dir, and presses the Enter key to send the string dir to the server. 
  2. The server receives the string and determines whether it can execute the command dir. dir is not one of the internal commands implemented by the server, so is passed on to DOS as an external command.
  3. DOS executes the dir command, the results are wrapped in HTML and returned to the browser.
  4. The browser receives the HTML text and renders it following the HTML instructions.

Browser sends user input "dir" to server. Server fails to recognize "dir" as an internal server command and passes "dir" text to DOS.

DOS executes "dir" returning generated text back to server. Server returns text from DOS to browser.

The C++ server function EXECUTEcmd that distinguishes between internal server and external DOS commands is given below. It receives two strings, command is the command such as dir, DOSlow, echo, etc. and input is the input to the command or program.

For example, if  help were entered in the browser, command="help" and input="" that is the empty string. The function attempts to match command with all the internal commands ("HELP", "STOP", and "VERSION") so would call the function HELP and return the output of the HELP function. If echo hello is entered at the browser then command="echo" and input="hello" and a call of ECHO("hello") is executed.

If DOSlow ABCD 1234 were entered in the browser, command = "DOSlow" and input="ABCD 1234". None of the internal commands ("HELP", "STOP", and "VERSION") would match with DOSlow so the command and input would be passed to the DOS function and the execution output returned.

If dir *.exe is entered at the browser, since it matches neither HELP, STOP, VERSION, or ECHO a call of DOS("dir", "*.exe") is executed.
 

EXECUTEcmd - Handle Server and DOS commands
extern "C" char * EXECUTEcmd(char command[], char input[]) {
        if (STRcmp("HELP", STRupcase(command)) == 0)
                return HELP();
        if (STRcmp("STOP", STRupcase(command)) == 0)
                return STOP();
        if (STRcmp("VERSION", STRupcase(command)) == 0)
                return VERSION();
        if (STRcmp("ECHO", STRupcase(command)) == 0)
                return ECHO(input);
        return DOS(command, input);
}

How to do the Project

The project can be done in a combination of three approaches:
  1. Extending the server by adding new of modifying existing capabilities.
  2. Converting existing C++ functions to Assembler.
  3. Adding DOS commands, essentially writing a standalone 16 or 32-bit program that can be executed at the DOS prompt.

Extending the Server

The server can be extended by adding new internal commands similar to "HELP", "STOP", and "VERSION". The extensions can be original or expanding upon existing commands, for example the REGDUMP command which is already functional but only partially completed. The REGDUMP command displays the CPU eAx, eBx and eCx register values in hexadecimal, it can be extended to display additional registers, the stack, memory, etc. The REGDUMP command display is at right.

It is important to note that ALL commands must return a pointer to a string. The characters of the string are passed back by the server to the browser for display. In the How the Server Works section, the dir command returns a copy of a string from DOS, which the server returns back to the browser.

The method of extending the server is:

  1. Add the command to be recognized and the function call to the EXECUTEcmd function. To add the VERSION command enter the two lines of:

  2.         if (STRcmp("VERSION", STRupcase(command)) == 0)
                    return VERSION();
    which matches command enter from the browser with "VERSION" then calls the VERSION() function.
  3. Add the prototype of the function called when the command is entered, for the VERSION function:

  4.         extern "C" char *VERSION(void);
  5. Write a C++ pseudocode then the assembler version in one file of the same name as the function. It is not required that function and file be the same but reduces confusion.

Converting Existing C++ Functions to Assembler

The project is designed to be completed in several steps:

  1. Start simple - The base project is written in C++ and some Assembler examples. Get this to work first by assembling REGDUMP.asm by:

  2.            ml /c /coff /Zi /Cp REGDUMP.asm
  3. Remove one C++ function at a time and replace it with the corresponding Assembler function.
  4. Test project Assembler functions using same inputs as in Step 1. The results should be the same.
Testing - Note that nearly all the functions in the student.cpp file are used whenever a command is received from a browser so it is often difficult to determine problem sources. Suggestions:
  1. Implement only one function at a time.
  2. Test function in a trivial case outside the server when possible.
  3. Link into the server and verify that the function works for the system level test .
 Test STRhex function in C++
#include <iostream.h>
#include <stdlib.h>

// STRnew - Returns a pointer to memory allocated of specified length 
extern "C" char * STRnew(int length) {
        return (new char[length]);
}

// STRcpy - Copy s1 to s2, assumes that size of s2 is equal or larger than s1.
extern "C" void STRcpy(const char s1[], char s2[]) {
        int i1=0, i2=0;

        while( s1[i1] != '\0')
                s2[i2++] = s1[i1++];
        s2[i2] = '\0';
}

// STRhex - Convert int to hexadecimal string, return a pointer to string. 
//  25 points
extern "C" char * STRhex(unsigned int n) {
        char hex[]="00000000", *retstr=STRnew(10);
        int i=7, hexdigit;

        while(n != 0 && i >= 0) {
                hexdigit = n % 16;
                n = n / 16;
                if (hexdigit <= 9)
                        hex[i] = hexdigit + '0';
                else
                        hex[i] = hexdigit + 'A' - 10;
                i--;
        }
        STRcpy(hex, retstr);
        return retstr;
}

void main(void) {
        cout << "STRhex( 12 ) = " << STRhex( 12 ) << "\n";
}
STRhex( 12 ) = 0000000C

Adding DOS Commands

Writing a program that runs under DOS but is executed by the server offers the greatest opportunity for accessing hardware and using DOS functions. The DOS commands can be any executable program that uses standard input and standard output. Otherwise, the programs can be very similar to those implemented earlier in this course, except of course that GetDec, PutDec, etc. cannot be used for input and output. Normally, a 16-bit program would be written entirely in Assembler to use DOS or BIOS interrupts. If implementing as a 32-bit program, the io.obj functions (PutDec, GetDec, PutStrng, and PutChar) can be used for input/output, a mixed C++ and Assembler program, or C library functions (see notes on Windows programming, the C Functions example) can be invoked but DOS and BIOS cannot be used.

16-bit - An example 16-bit DOS program is DOSecho.asm which echoes all input until a ASCII 13 (carriage return) is input. The program runs under DOS and is given below. The suggested steps to implementing a DOS command are:

  1. Write a C++ or other pseudocode algorithm then convert to pure Assembler. The DOSecho is a trivial example of a DOS program. Other examples in the project directory are:
  2. Assemble, for example, DOSdir.asm:
  3. Test that the program works at the DOS command line and can redirect all input and output. For example, test DOSdir to list all ASM files to standard output:
  4. Copy the executable to the same directory as the server. If the server directory (not the Debug directory) was C:\C335\Project then to copy the DOSlow.exe program there:

  5.            copy DOSlow.exe C:\C335\Project
  6. In the browser, enter the name of the program and one line of program input on the same line. There is no opportunity for interaction with the program since all input must sent to the DOS program when executed from the browser.

32-bit - One example is Homework 7, a 32-bit program that reads one line of input using GetDec from the io.obj file. Any 32-bit program can use GetDec, PutDec, PutStrng, and PutChar as used fin homeworks. Note that 32-bit programs cannot invoke DOS or BIOS interrupts. 

C library functions can also be used directly by defining the prototype to invoke the function and linking in the C librabry. The DOSecho32.asm below echoes all input until an ASCII 10 (new line) is input. For a more realistic example, see notes on Windows programming, the C Functions example. The steps necessary to use C library functions in DOSecho32.asm are:

  1. Copy the MSVCRT.LIB to the same directory as DOSecho32.asm:
  1. v:\common\user\c335\assembler32
  2. ml /coff /Zi DOSecho32.asm /link MSVCRT.LIB /subsystem:console
  3. DOSecho32
Title DOSecho.asm 16-bit 
; Echo standard input 
; until ASCII 13
.286
.model small

.code

Main    Proc    near
@@do:
	Mov	Ah, 8
	Int	21h
	Mov	Ah, 2
	Mov	Dl, Al
	Int	21h
@@while:		
	Cmp	Al, 13
	Jne	@@do

       	Mov     Ah, 4ch 
        Int     21h
Main    Endp
	End     Main
// Echo standard input  
// until ASCII 10 input

int getchar(void);
int putchar(int c);

void main(void) {
 while(Al=getchar()!='\n')
   putchar(Al);
}
Title   DOSecho32.asm 32-bit
; Echo standard input until ASCII 10
.386
.model flat, stdcall
option casemap:none
.nolist
include v:\common\user\c335\masm32\include\windows.inc
include v:\common\user\c335\masm32\include\kernel32.inc  
include v:\common\user\c335\masm32\include\masm32.inc
includelib v:\common\user\c335\masm32\lib\kernel32.lib
includelib v:\common\user\c335\masm32\lib\masm32.lib
.list

getchar	proto	near C
putchar	proto	near C, char : dword

.code
main	proc	near
@@while:
	invoke	getchar
	cmp	Al, 10
	jne	@@do
	jmp	@@endwhile
   @@do:
	invoke	putchar, eAx
	jmp	@@while
@@endwhile:

     	invoke 	ExitProcess, 0	
main	endp
	end 	main
 

Note that the output will be displayed in the browser just as it appears when executed at the DOS prompt. HTML tags can be added to the text to improve the appearance when displayed by the browser. For example, server output will be displayed as bold faced by the browser if the text is surrounded by <b> and </b>.  For example, <b>bold stuff</b> will appear as bold stuff.

Suggested Command Extensions

Other possible extensions are below, you are welcome to devise other, long dreamed of commands. These may be implemented either internally by adding to the server or externally as standalone DOS programs. Before implementing any extension, please get the instructors approval, advice, and point value directly or via email to verify the appropriateness and viability of the extension.

Software Download

There are several files required for the minimal server, the following will create a project directory and copy necessary files by the steps of:
  1. Download the self expanding file project.exe to your computer. 
  2. From DOS, change directory to where project.exe was saved and enter project. Or from Windows Explorer, click on the icon . Windows 95, 98, and ME users should extract files to a directory named Project.

Compiling, Linking and Execution

A Visual C++ project file is located in the project directory. The steps to executing the minimal server are:
  1. Assembler - Only necessary if you have changed any Assembly files.
  2. Visual C++
  3. Browser, see the Overview section above for the visuals.

Testing

The obvious method of testing the server is to make changes and run the server. However, if it fails, the difficulty is determining what conditions existed at the point of failure. One method of testing functions translated from C++ to Assembler is to first test using a small program that exercises the function. For example, suppose that you had written STRcat and wanted to test. A reasonable test would be the C++ program below which allocates enough storage for the s string to have 3 characters "abc" concatenated at the end of  "123".
#include <iostream.h>
extern "C" void STRcat(const char t[], char s[]);

void main(void) {
    char *t="abc", 
         s[10]={'1', '2', '3', '\0'};    // "123" with 6 bytes of storage for "abc"

    cout << STRcat(t, s);                // Output "123abc"
}
Another test function is for CHARrep:
#include <iostream.h>
extern "C" void CHARrep(char s[], char c1, char c2);

void main(void) {
    s[10]={'a', 'b', 'a', 'b', 'a', '\0'}; 

    cout << CHARrep(s, 'a', 'x');                // Replace 'a' with 'x'
}
The parameters would be pushed onto the stack, 4 bytes for each parameter. The parameters can be accessed using either of the following:
s       Equ   dword ptr [eBp+8]   
c1      Equ   byte ptr  [eBp+12]
c2      Equ   byte ptr  [eBp+16]
CHARrep	proto near C, s:near ptr dword, c1:byte, c2:byte

When Your Server Crashes

Possible causes of a server crash or hang Recovery from crash Prevention

File List

There are several files supplied with the project. By default, these are placed in the project directory when Project.exe is executed to copy the project files. There are also several example C++ callable functions and DOS programs included. These files and functions are:

Notes and Warnings (FAQs)

Strings - All strings are terminated by a '\0'. Strings are always passed by reference to functions so a function will receive a pointer to the string. As an example, if the leftmost parameter to the function is a string, to move the first character of the string into the Al register:
Mov     eSi, [eBp+8]
Mov     Al, [eSi]
Function Results - Almost all functions return type is char * meaning that they return a pointer to memory that contains a string. Pointers are 32 bit and should be returned in the eAx register. Generally, any function that creates a string should dynamically allocate the memory storage used to hold the string by calling the STRnew function. See below.

Memory Allocation and STRnew - The server inputs strings of characters from the browser and returns string of characters back to the browser. The memory used to hold strings is assumed to be dynamically allocated using the new function. The server dynamically deallocates all strings when no longer needed. If the server attempts to deallocate memory that doesn't exist (has not been allocated) some form of a memory allocation error will occur.

For example, suppose that the VERSION function returned a string defined as a local variable versionstr. When the function exits it returns a pointer to the string memory but the storage has already been deallocated by the function return.

extern "C" char * VERSION(void) {
     char *versionstr = "<h2>Remote Server Version 2000</h2>";
                                                                                        // Allocate on entry, deallocate on exit
     return versionstr;
}
STRnew is a C++ function that allocates memory for type char *.  The STRnew returns a char * pointer to memory allocated for strings. For example, to allocate 100 characters and point to it:
char *retstr = STRnew(100);
The VERSION function should allocate enough non-local memory to the string that is be returned (retstr) and copy the characters into the memory allocated. When the VERSION function returns the allocated memory pointed to by retstr still exists until deallocated by the server when finished with the string.
extern "C" char * VERSION(void) {
     static char *versionstr = "<h2>Remote Server Version 2000</h2>";   
                                                                               // Local string allocated on entry/deallocated on exit
     char *retstr = STRnew(STRlen(versionstr)+1);    // Allocate non-local memory
     STRcpy(versionstr, retstr);                                  // Copy local string to memory pointed to by retstr
     return retstr;                                                       // Return pointer to string
}
See also the assembler listing for Version.asm file that corresponds to the above Version function.

Registers - Remember that C++ does NOT save all registers used in functions. Only the segment registers (CS, DS, Es, and SS), eSi, eDi, eBP, and eSP are protected. Calling a C++ function may destroy the contents of any other registers.

It is your responsibility to save any registers before pushing function parameters and restore after the function call. In the following example, the eCX register is saved and restored.

                Mov     eCx, 10
        Do:
                Push    eCx             ; Save eCx before parameters pushed

                Push    Offset String
                Call    puts
                Add     eSp, 4

                Pop     eCx             ; Restore eCx

       While:   Loop    Do
Stack - All functions used within a C++ program must use extern "C" parameter passing methods used throughout this course. With "C" parameter passing, the parameters are removed from the stack by the calling function, for example, ADD eSp, 4 removes 4 bytes of parameters. In the above example, after calling the puts function, remove 4 bytes of parameters. Remember that Visual C++ pushes 32 bit parameters in right-to-left order.

32 bit registers and parameters - Also remember that Visual C++ has 32 bit parameters and flat addressing. For functions that are called or call Visual C++ functions, always use the C++ conventions discussed earlier in the course (see Chapter 7 and Homework 8).

Pointer versus Character Value parameters - Some of the functions use 32 bit pointers to strings (reference parameters) and some, such as CHARrep, also use character values. Since pointers are 32 bits, each one obviously occupies 4 bytes on the stack. However, characters are bytes so how many bytes should they occupy on the stack? With VC++, all parameters on the stack seem to be 4 bytes but which of the 4 bytes holds the character parameter? The following is the definition of the stack parameters for CHARrep, where s is a pointer, c1 and c2 are character values, perhaps c1 == 'A' and c2 == 'B'. Note that c1 and c2 are defined as byte. Character parameters are pushed on the stack by Visual C++ using 4 bytes with the character in the lowest byte.

; extern "C" void CHARrep( char s[], char c1, char c2);
                                      Stack Offset
s     Equ   dword ptr [eBp+8]                8-11       12 34 56 78   Pointer s
c1    Equ   byte ptr [eBp+12]               12-15       41 00 00 00   Character c1='A'
c2    Equ   byte ptr [eBp+16]               16-19       42 00 00 00   Character c2='B'
Interrupts  - The text provides an overview of interrupts. More complete information on interrupts is at: DOS Interrupts - DOS interrupts CANNOT generally be executed through a Visual C++ 6.0 function call but the server can execute DOS programs that use interrupts. If function f contains a DOS Int 21h and is called directly from C++ program then most likely the Windows operating system will crash. Note that it is possible, Homework 8 is an example, to call assembler functions from C++, the assembler function just cannot use BIOS or DOS function (i.e. the Int 21h). Standalone Assembler programs written for DOS, as have been most of the programs in the course, can use BIOS or DOS interrupts. The difference is that calling a function from C++ executes in 32 bit but the BIOS and DOS functions are 16 bit.

Bios Interrupts - For DOS programs only. Keyboard and video interrupts CANNOT generally be used because input and output must be through standard input and output. Other BIOS interrupts should be useable under Windows 95/98, such as INT 12h to get the system memory size.

Input and Output - Assembler programs can perform standard input and output using DOS Int 21h, function 1, 2, 7, 8 and 9 (see text, page 464 or Ralf Brown's INTerrupt list). Assembler functions that are part of a C++ program can call a C++ function that has been written to perform input and output. The GetDec, PutDec, etc. functions DO NOT support redirection. The provided DOSio.asm contains unsigned integer and string input and output functions that can be used by DOS programs.

Server Files - The server can only access files that are in the project directory. Any executable programs must be copied to the project directory (C:\C335\Project) for the server to execute. To execute DOS commands, command.com may need to be copied into the project directory. If DOS commands (e.g. dir, ver, ...) fail, copy command.com to the project directory at the DOS prompt by:

copy c:\command.com c:\C335\Project
Special characters - The browsers send certain many of the special characters as the hexadecimal code (e.g. ':' sent as %3A, ',' sent as %2C, '\' as %5C, etc.). These are not translated currently so cannot be used in commands. Converting %3A back to ':' would make a nice addition to the project.

Linking Errors - The most common problem is functions that exist more than once. One way this can occur is if you add the student.cpp to the project. Since the server.cpp file already includes the student.cpp file by:

#include "student.cpp"
adding student.cpp to the project would make two occurrences. Solution is to remove student.cpp from the workspace, don't delete the file itself but take it out of the workspace so only one copy of student.cpp is compiled.

Building and Compiling - The one file that you'll be editing is student.cpp which needs to be saved each time changes are made. To compile the full server use Rebuild All to ensure that all changed files are recompiled.

Grading

Teams

Teams consist of no more than two individuals who will receive that same score for the project. A team of two are expected to complete twice that of an individual. In the case that only one of the team members contribute significantly, each team member's grade will be adjusted accordingly.

Turn In

All files used for the project should be emailed to the instructor along with instructions for executing each part of the project you completed. Cover page and instructions can be composed using MS Word.

Cover page  - Team member names, date, and Project.

For each function or program:

  • Source listing (what you typed).
  • Typed specification of its purpose.
  • Execution results of each function or program as a screen shot of browser input and output after execution.
  • Instructions of the testing procedure to verify its correct operation according to its specification. This may require an explanation of the conditions under which the function was executed to produce the output given.
  • email
    1. Directory - Create a directory named PROJECT that contains all project files and instructions.
    2. Zip - Use WINZIP to zip all files and subdirectories of the PROJECT directory.
    3. email - email the zipped project to yourself as a test.
    4. Test - Unzip the emailed project zip file to a new directory and make certain that it still works.
    5. email - email the zipped project to rwisman@ius.edu

    Document last modified: