ECE3166: Advanced Microprocessors AM2

AM2: Programming with Contemporary Instruction Set

Duration: 3 hours

Components: Lab exercises and report.

Objectives: (a) To examine how protected-mode programming is done on x86 based computer. (b) To evaluate the advantages of protected-mode programming compared to real-mode programming in x86 microprocessor. (c) To develop protected-mode assembly language programs which utilize the floating- point unit (FPU) on x86 based computer. (d) To develop Win32 Console programs based on x86 assembly language.

1. Floating-Point Unit The Intel 8086 processor was designed to handle only integer arithmetic. This turned out to be a problem for graphics and calculation-intensive software using floating-point calculations. It was possible to emulate floating-point arithmetic purely through software, but the performance penalty was severe. Programs such as AutoCad (by Autodesk) demanded a more powerful way to perform floating-point math. Intel sold a separate floating-point coprocessor chip named the 8087, and upgraded it along with each processor generation. Beginning from Intel 80486, the floating-point hardware was integrated into the processor and known as the floating-point unit (FPU).

1.1 FPU Register Stack The FPU does not use the general-purpose registers (EAX, EBX, etc.). Instead, it has its own set of registers called a register stack. It loads values from memory into the register stack, performs calculations, and stores stack values into memory. FPU instructions evaluate mathematical expressions in postfix format. The following, for example, is called an infix expression: (5 * 6) + 4. The postfix equivalent is 5 6 * 4 + The infix expression (A + B) * C requires parenthesis to override the default precedence rules (multiplication before addition). The parenthesis is not required in the equivalent postfix expression: A B + C *

1.1.1 Expression Stack A stack holds intermediate values during the evaluation of postfix expressions. Figure 1.1 shows the steps required to evaluate the postfix expression 5 6 * 4 –. The stack entries are labelled ST(0) and ST(1), with ST(0) indicating where the stack pointer would normally be pointing.

Page 1 of 15 ECE3166: Advanced Microprocessors AM2

Figure 1.1: Evaluating the Postfix Expression 5 6 * 4 – .

Table 1.1 contains some examples of translating infix to equivalent postfix expressions. Table 1.1 Infix to Postfix Examples Infix Postfix A + B A B + (A - B) / D A B - D / (A + B) * (C + D) A B + C D + * ((A + B) / C) * (E - F) AB + C / EF - *

1.2 FPU Data Registers The FPU has eight individually addressable 80-bit data registers named R0 through R7 (see Figure 2). Together, they are called a register stack. A three-bit field named TOP in the FPU status word identifies the register number that is currently the top of the stack. In Figure 1.2, for example, TOP equals binary 011, identifying R3 as the top of the stack. This stack location is also known as ST(0) (or simply ST) when writing floating-point instructions. The last register is ST(7).

Figure 1.2: Floating-Point Data Register Stack.

As we might expect, a push operation (also called load) decrements TOP by 1 and copies an operand into the register identified as ST(0). If TOP equals 0 before a push, TOP wraps around to register R7. A pop operation (also called store) copies the data at ST(0) into an operand, then adds 1 to TOP. If TOP equals 7 before the pop, it wraps around to register R0.

Page 2 of 15 ECE3166: Advanced Microprocessors AM2

If loading a value into the stack would result in overwriting existing data in the register stack, a floating-point exception is generated. Figure 1.3 shows the same stack after 1.0 and 2.0 have been pushed (loaded) on the stack.

Figure 1.3: FPU Stack after Pushing 1.0 and 2.0.

Although it is interesting to understand how the FPU implements the stack using a limited set of registers, we need only focus on the ST(n) notation, where ST(0) is always the top of stack. From this point forward, we refer to stack registers as ST(0), ST(1), and so on. Instruction operands cannot refer directly to register numbers.

Floating-point values in registers use the IEEE 10-byte extended real format (also known as temporary real). When the FPU stores the result of an arithmetic operation in memory, it translates the result into one of the following formats: integer, long integer, single precision (short real), double precision (long real), or packed binary-coded decimal (BCD).

1.3 Floating-Point Instruction Set The FPU instruction set is somewhat complex, so we will attempt here to give you an overview of its capabilities, along with specific examples that demonstrate code typically generated by compilers. In addition, we will see how you can exercise control over the FPU by changing its rounding mode. The instruction set contains the following basic categories of instructions: • Data transfer • Basic arithmetic • Comparison • Transcendental • Load constants (specialized predefined constants only) • x87 FPU control • x87 FPU and SIMD state management

Floating-point instruction names begin with the letter F to distinguish them from CPU instructions. The second letter of the instruction mnemonic (often B or I) indicates how a memory operand is to be interpreted: B indicates a BCD operand, and I indicates a binary integer operand. If neither is specified, the memory operand is assumed to be in real-number

Page 3 of 15 ECE3166: Advanced Microprocessors AM2 format. For example, FBLD operates on BCD numbers, FILD operates on integers, and FLD operates on real numbers. 1.3.1 Operands A floating-point instruction can have zero operands, one operand, or two operands. If there are two operands, one must be a floating-point register. There are no immediate operands, but certain predefined constants (such as 0.0, _and log2 10) can be loaded into the stack. General-purpose registers such as EAX, EBX, ECX, and EDX cannot be operands. (The only exception is FSTSW, which stores the FPU status word in AX.) Memory-to-memory operations are not permitted. Integer operands must be loaded into the FPU from memory (never from CPU registers); they are automatically converted to floating-point format. Similarly, when storing floating-point values into integer memory operands, the values are automatically truncated or rounded into integers.

1.3.2 Initialization (FINIT) The FINIT instruction initializes the FPU. It sets the FPU control word to 037Fh, which masks (hides) all floating-point exceptions, sets rounding to nearest even, and sets the calculation precision to 64 bits. We recommend calling FINIT at the beginning of your programs, so you know the starting state of the processor.

1.3.3 Floating-Point Data Types Let’s quickly review the floating-point data types supported by MASM (QWORD, TBYTE, REAL4, REAL8, and REAL10), listed in Table 1.2. You will need to use these types when defining memory operands for FPU instructions. For example, when loading a floating-point variable into the FPU stack, the variable is defined as REAL4, REAL8, or REAL10: .data bigVal REAL10 1.212342342234234243E+864 .code fld bigVal ; load variable into stack

Table 1.2: Intrinsic Data Types. Type Usage QWORD 64-bit integer TBYTE 80-bit (10-byte) integer REAL4 32-bit (4-byte) IEEE short real REAL8 64-bit (8-byte) IEEE long real REAL10 80-bit (10-byte) IEEE extended real

1.3.4 Reading and Writing Floating-Point Values In this experiment, the following two procedures for floating-point input-output (created by William Barrett of San Jose State University) are used: ReadFloat: Reads a floating-point value from the keyboard and pushes it on the floating- point stack. It accepts a wide variety of floating-point formats. Some examples are shown below: 35 3.5E005 +35. -3.5E+5 -3.5 3.5E-4 .35 +3.5E-4 3.5E5 WriteFloat: Writes the floating-point value at ST(0) to the console window in exponential format.

Page 4 of 15 ECE3166: Advanced Microprocessors AM2

ShowFPUStack: Another useful procedure, written by James Brink of Pacific Lutheran University, displays the FPU stack. It is called with no parameters: call ShowFPUStack Example 1.1 The following example program pushes two floating-point values on the FPU stack, displays it, inputs two values from the user, multiplies them, and displays their product: TITLE Example 1.1 (ex11.asm) INCLUDE Irvine32.inc INCLUDE macros.inc .data first REAL8 123.456 second REAL8 10.0 third REAL8 ? .code main PROC finit ; initialize FPU ; Push two floats and display the FPU stack. fld first fld second call ShowFPUStack ; Input two floats and display their product. mWrite "Please enter a real number: " call ReadFloat mWrite "Please enter a real number: " call ReadFloat fmul ST(0),ST(1) ; multiply mWrite "Their product is: " call WriteFloat call Crlf exit main ENDP END main Note: Please refer to Appendix A on how to build and run this program.

Sample input/output (user input shown in bold type): ------FPU Stack ------ST(0): +1.0000000E+001 ST(1): +1.2345600E+002 Please enter a real number: 3.5 Please enter a real number: 4.2 Their product is: +1.4700000E+001

Example 1.2 Let’s code the expression valD = -valA + (valB * valC). A possible step-by-step solution is: Load valA on the stack and negate it. Load valB into ST(0), moving valA down to ST(1). Multiply ST(0) by valC, leaving the product in ST(0). Add ST(1) and ST(0) and store the sum in valD: TITLE Example 1.2 (ex12.asm) INCLUDE Irvine32.inc INCLUDE macros.inc .data valA REAL8 1.5

Page 5 of 15 ECE3166: Advanced Microprocessors AM2 valB REAL8 2.5 valC REAL8 3.0 valD REAL8 ? ; +6.0 .code main PROC finit fld valA ; ST(0) = valA fchs ; change sign of ST(0) fld valB ; load valB into ST(0) fmul valC ; ST(0) *= valC fadd ; ST(0) += ST(1) fstp valD ; store ST(0) to valD exit main ENDP END main Please refer to Appendix B on how to debug this program.

Exercise 1.1 Write a program that prompts the user for the radius of a circle. Calculate and display the circle’s circumference and area. Use the ReadFloat and WriteFloat procedures. Use the FLDPI instruction to load  onto the register stack. Please refer to Appendix A on how to create, build and run your program.

2. Win32 Console Programming On the surface, 32-bit console mode programs look and behave like 16-bit MS-DOS programs running in text mode. There are differences, however: The former runs in 32-bit protected mode, whereas MS-DOS programs run in real-address mode. They use different function libraries. Win32 programs call functions from the same library used by graphical Windows applications. MS-DOS programs use BIOS and MS-DOS interrupts that have existed since the introduction of the IBM-PC.

2.1 Application Programming Interface An Application Programming Interface (API) is a collection of types, constants, and functions that provide a way to directly manipulate objects through programming. Therefore, the Win32 API lets you tap into the functions in the 32-bit version of MS-Windows. The Irvine32 link library used in this experiment is completely built on Win32 console functions. It is compatible with Win32 API functions and can be used for basic input output, simulations, timing, and other useful operations. Table 2.1 gives a complete list of procedures in the Irvine32 link library. Table 2.1: Procedures in the Irvine32 link library Procedure Description CloseFile Closes a disk file that was previously opened. Clrscr Clears the console window and locates the cursor at the upper left corner. CreateOutputFile Creates a new disk file for writing in output mode. Crlf Writes an end-of-line sequence to the console window. Delay Pauses the program execution for a specified n -millisecond interval. DumpMem Writes a block of memory to the console window in hexadecimal. DumpRegs Displays the EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EFLAGS, and EIP registers in hexadecimal. Also displays the most common CPU status flags. GetCommandTail Copies the program’s command-line arguments (called the command tail) into an array of bytes. GetDateTime Gets the current date and time from the system.

Page 6 of 15 ECE3166: Advanced Microprocessors AM2

GetMaxXY Gets the number of columns and rows in the console window’s buffer. GetMseconds Returns the number of milliseconds elapsed since midnight. GetTextColor Returns the active foreground and background text colors in the console window. Gotoxy Locates the cursor at a specific row and column in the console window. IsDigit Sets the Zero flag if the AL register contains the ASCII code for a decimal digit (0–9). MsgBox Displays a popup message box. MsgBoxAsk Display a yes/no question in a popup message box. OpenInputFile Opens an existing disk file for input. ParseDecimal32 Converts an unsigned decimal integer string to 32-bit binary. ParseInteger32 Converts a signed decimal integer string to 32-bit binary. Random32 Generates a 32-bit pseudorandom integer in the range 0 to FFFFFFFFh. Randomize Seeds the random number generator with a unique value. RandomRange Generates a pseudorandom integer within a specified range. ReadChar Waits for a single character to be typed at the keyboard and returns the character. ReadDec Reads an unsigned 32-bit decimal integer from the keyboard, terminated by the Enter key. ReadFromFile Reads an input disk file into a buffer. ReadHex Reads a 32-bit hexadecimal integer from the keyboard, terminated by the Enter key. ReadInt Reads a 32-bit signed decimal integer from the keyboard, terminated by the Enter key. ReadKey Reads a character from the keyboard’s input buffer without waiting for input. ReadString Reads a string from the keyboard, terminated by the Enter key.

SetTextColor Sets the foreground and background colors of all subsequent text output to the console. Str_compare Compares two strings. Str_copy Copies a source string to a destination string. Str_length Returns the length of a string in EAX. Str_trim Removes unwanted characters from a string. Str_ucase Converts a string to uppercase letters. WaitMsg Displays a message and waits for a key to be pressed. WriteBin Writes an unsigned 32-bit integer to the console window in ASCII binary format. WriteBinB Writes a binary integer to the console window in byte, word, or doubleword format. WriteChar Writes a single character to the console window. WriteDec Writes an unsigned 32-bit integer to the console window in decimal format. WriteHex Writes a 32-bit integer to the console window in hexadecimal format. WriteHexB Writes a byte, word, or doubleword integer to the console window in hexadecimal format WriteInt Writes a signed 32-bit integer to the console window in decimal format. WriteStackFrame Writes the current procedure’s stack frame to the console. WriteStackFrameNam Writes the current procedure’s name and stack frame to the console. e WriteString Writes a null-terminated string to the console window. WriteToFile Writes a buffer to an output file. WriteWindowsMsg Displays a string containing the most recent error generated by MS- Windows.

Some of the procedures that are used in this experiment are described in the following sections.

Page 7 of 15 ECE3166: Advanced Microprocessors AM2

2.1.1 MsgBox The MsgBox procedure displays a graphical popup message box with an optional caption. (This works when the program is running in a console window.) Pass it the offset of a string in EDX, which will appear in the inside the box. Optionally, pass the offset of a string for the box’s title in EBX. To leave the title blank, set EBX to zero. Sample call: .data caption db "Dialog Title", 0 HelloMsg BYTE "This is a pop-up message box.", 0dh,0ah BYTE "Click OK to continue...", 0 .code mov ebx,OFFSET caption mov edx,OFFSET HelloMsg call MsgBox

Sample output:

2.1.2 MsgBoxAsk The MsgBoxAsk procedure displays a graphical popup message box with Yes and No buttons. (This works when the program is running in a console window.) Pass it the offset of a question string in EDX, which will appear in the inside the box. Optionally, pass the offset of a string for the box’s title in EBX. To leave the title blank, set EBX to zero. MsgBoxAsk returns an integer in EAX that tells you which button was selected by the user. The value will be one of two predefined Windows constants: IDYES (equal to 6) or IDNO (equal to 7). Sample call: .data caption BYTE "Survey Completed",0 question BYTE "Thank you for completing the survey." BYTE 0dh,0ah BYTE "Would you like to receive the results?",0 .code mov ebx,OFFSET caption mov edx,OFFSET question call MsgBoxAsk ;(check return value in EAX)

Sample output:

Page 8 of 15 ECE3166: Advanced Microprocessors AM2

2.1.3 ReadString The ReadString procedure reads a string from the keyboard, stopping when the user presses the Enter key. Pass the offset of a buffer in EDX and set ECX to the maximum number of characters the user can enter, plus 1 (to save space for the terminating null byte). The procedure returns the count of the number of characters typed by the user in EAX. Sample call: .data buffer BYTE 21 DUP(0) ; input buffer byteCount DWORD ? ; holds counter .code mov edx,OFFSET buffer ; point to the buffer mov ecx,SIZEOF buffer ; specify max characters call ReadString ; input the string mov byteCount,eax ; number of characters ReadString automatically inserts a null terminator in memory at the end of the string. The following is a hexadecimal and ASCII dump of the first 8 bytes of buffer after the user has entered the string “ABCDEFG”: 41 42 43 44 45 46 47 00 ABCDEFG The variable byteCount equals 7.

2.1.4 WriteString The WriteString procedure writes a null-terminated string to the console window. Pass the string’s offset in EDX. Sample call: .data prompt BYTE "Enter your name: ",0 .code mov edx,OFFSET prompt call WriteString

2.1.5 WriteDec The WriteDec procedure writes a 32-bit unsigned integer to the console window in decimal format with no leading zeros. Pass the integer in EAX. Sample call: mov eax,295 call WriteDec ; displays: "295"

2.1.6 WriteToFile The WriteToFile procedure writes the contents of a buffer to an output file. Pass it a valid file handle in EAX, the offset of the buffer in EDX, and the number of bytes to write in ECX. When the procedure returns, if EAX is greater than zero, it contains a count of the number of bytes written; otherwise, an error occurred. The following code calls WriteToFile: BUFFER_SIZE = 5000 .data

Page 9 of 15 ECE3166: Advanced Microprocessors AM2 fileHandle DWORD ? buffer BYTE BUFFER_SIZE DUP(?) .code mov eax,fileHandle mov edx,OFFSET buffer mov ecx,BUFFER_SIZE call WriteToFile

The following pseudocode describes how to handle the value returned in EAX after calling WriteToFile: if EAX = 0 then error occurred when writing to file call WriteWindowsMessage to see the error else EAX = number of bytes written to the file endif

Example 2 The following program creates a file in output mode, asks the user to enter some text, writes the text to the output file, reports the number of bytes written, and closes the file. It checks for errors after attempting to create the file: TITLE Creating a File (CreateFile.asm) INCLUDE Irvine32.inc BUFFER_SIZE = 501 .data buffer BYTE BUFFER_SIZE DUP(?) filename BYTE "output.txt",0 fileHandle HANDLE ? stringLength DWORD ? bytesWritten DWORD ? str1 BYTE "Cannot create file",0dh,0ah,0 str2 BYTE "Bytes written to file [output.txt]:",0 str3 BYTE "Enter up to 500 characters and press" BYTE "[Enter]: ",0dh,0ah,0

.code main PROC ; Create a new text file. mov edx,OFFSET filename call CreateOutputFile mov fileHandle,eax

; Check for errors. cmp eax, INVALID_HANDLE_VALUE ; error found? jne file_ok ; no: skip mov edx,OFFSET str1 ; display error call WriteString jmp quit

; Ask the user to input a string. file_ok: mov edx,OFFSET str3 ; "Enter up to ...."

Page 10 of 15 ECE3166: Advanced Microprocessors AM2

call WriteString mov ecx,BUFFER_SIZE ; Input a string mov edx,OFFSET buffer call ReadString mov stringLength,eax ; counts chars entered

; Write the buffer to the output file. mov eax,fileHandle mov edx,OFFSET buffer mov ecx,stringLength call WriteToFile mov bytesWritten,eax ; save return value call CloseFile

; Display the return value. mov edx,OFFSET str2 ; "Bytes written" call WriteString mov eax,bytesWritten call WriteDec call Crlf quit: exit main ENDP END main Note: Please refer to Appendix A on how to create, build and run this program

Exercise 2.1 Modify the program in Example 2 so that it will display a graphical popup message box to ask the user whether the text file should be created. If the user selects “Yes”, the text file is created and the program terminates. Otherwise, the program will terminate directly.

References 1. Kip R. Irvine, “Assembly Language for x86 Processors”, 6th Edition, Prentice-Hall Inc., U.S.A., 2010. 2. Kip R. Irvine, Assembly Language for x86 Processors, 6th Edition [Online]. Available: http://kipirvine.com/asm/

Page 11 of 15 ECE3166: Advanced Microprocessors AM2

Important Note: Lab assessment will be carried out during the lab session. It is the student's responsibility to complete the given tasks and report to the lab instructors for assessment. (END) Appendix A

Opening a project Visual Studio and Visual C++ Express require assembly language source files to belong to a project, which is a kind of container. A project holds configuration information such as the locations of the assembler, linker, and required libraries. A project has its own folder, and it holds the names and locations of all files belonging to it. We have created a sample project folder in the c:\ECP4166\AM2\Example\Project_Sample directory, and its name is Project.

Do the following steps, in order: i. Start Visual Studio or Visual C++ Express. ii. First you will open an existing Visual Studio project file. If you're using Visual Studio, select Open Project from the File menu. Or, if you're using Visual C++ Express, select Open, and select Project/Solution. iii. Navigate to the c:\ECP4166\AM2\Example\Project_Sample folder and open the file named Project.sln. iv. In the Solution Explorer window, you will see the word Project. This is the name of a Visual Studio project. v. Next, you need to add the source code file (.asm) to the project. To do that, right-click on Project, select Add, select Existing Item, select the main.asm file, and click the Add button to close the dialog window. (You can use this sequence of commands in the future to add any asm file into a project.) vi. Next, you will open the main.asm file for editing. Double-click the main.asm file to open it in the editing window. (Visual Studio users may see a popup dialog asking for the encoding method used in the asm file. just click the OK button to continue.)

Build the Program Next, you will build (assemble and link) the sample program. Select Build Project from the Build menu. You should see messages indicating the build progress in the Output window for Visual Studio at the bottom of the screen. If you do not see these messages, the project has probably not been modified since it was last built. No problem--just select Rebuild Project from the Build menu.

Run the Program Select Start without Debugging from the Debug menu. The following console window should appear, although your window will be larger than the one shown here:

Page 12 of 15 ECE3166: Advanced Microprocessors AM2

Creating New Projects of Your Own Before long, you will want to create your own projects. The easiest way to do this is to copy the entire c:\ECP4166\AM2\Examples\Project_Sample folder to a new location. Copy it to a folder in which you have read/write permissions.

Appendix B

Microsoft Visual Studio 2008 Debugger This debugger lets you do the following:  Step through your program, viewing the source code  Set breakpoints in your code  View CPU registers and flags  View a disassembly of your program  Watch the values of program variables  View the runtime stack  Display blocks of memory  Perform remote debugging across a network

Open the Visual Studio Project file 1. Go to the the folder in your computer that contains the project. You may use the sample project located at c:\ECP4166\AM2\Example\Project_Sample. 2. Double-click the file named Project.sln. 3. If there is already an asm file in the project, right-click on the asm file and select Remove in the Solution Explorer window. When prompted by a dialog window, click the Remove button. 4. Right-click Project in the same window, select Add, and select Existing Item. 5. Select your asm file. It will be added to your project. 6. Select Save All from the File menu to save your project settings. 7. Double-click your asm file in Solution Explorer to open it in the editor.

Debugger Windows The debugging information is shown in the following windows after the debugger is invokved (press the F10 key). You can remove any debugger window by right-clicking on its Tab and selecting Hide. You can open any debugger window by selecting Windows from the Debug menu.

Source Window The Source window displays the program's source file. Note that the first instruction has a yellow arrow next to it. The arrow always indicates the next instruction that is about to execute.

Page 13 of 15 ECE3166: Advanced Microprocessors AM2

Watch Window Select Windows from the Debug menu, and select Watch 1. A Watch window is like an electronic spreadsheet that displays the names and values of selected variables. As you step through a program, you can see variables in this window change value. Currently the window is empty, but you can drag any program variable into the window with your mouse.

Click on the tab at the bottom of the screen labeled Watch 1 to bring it to the front. Drag the variables into the Watch window and note their current values. The values are currently displayed in decimal. You may select other format by right-clicking on the watch window and selecting the desired format from the popup menu.

Memory Window Select Windows from the Debug menu, and select Memory. The Memory window displays a raw dump of memory in either hexadecimal or decimal. It is particularly useful when working with array variables. For e.g., it can display the value of valD by typing &valD next to the Address label.

The & before the variable name means to interpret the variable name as an address. In assembly language, all labels are implied addresses. Variable names are case-sensitive in the debugger.

The Memory window displays a series of individual memory bytes, beginning at the address of valD. Right-click on the window, and select 64-bit Floating Point. Along the left side of the window is shown the address of the first value in each line.

Register Window Select Windows from the Debug menu, and select Register. The Register window displays the contents of the CPU registers. The floating-point register stack and flag values are not shown by default, but you can add them by right-click in the Register window and select Floating Point and Flags, respectively.

Debugging Program Step Over (F10) Pressing the F10 function key will single-step through the program. As you press F10, watch the yellow arrow in the Source window move from statement to statement. Notice the following as you step through the program:

Individual register names (in the Register window) turn red, indicating that they have been modified. Variables in the Watch window turn red when they are modified. Memory locations in the Memory window turn red when they are modified. As soon as you reach the exit statement and press F10, the debugger halts.

Step Into (F11) Another way to step through a program is to use the Trace (F11) command. It steps down into procedure calls. In contrast, the F10 key just executes procedure calls without tracing into the procedure code.

Page 14 of 15 ECE3166: Advanced Microprocessors AM2

Stopping and Restarting It's easy to either stop or restart your program inside the debugger while you're in the process of stepping through a program:  To restart the program, select Restart from the Debug menu. The program is reloaded into memory, so any changes previously made to variables in the Watch window are undone. Also, you have to retype the name of your variable in the Memory window, because it resets itself to a default address.  To stop debugging in the middle of a program, select Stop Debugging from the Debug menu.

Note: Be sure to stop the debugger before trying to modify and re-assemble your program's source code. Otherwise, the linker will refuse to assemble your EXE file, indicating that it's currently in use.

Page 15 of 15