ECP4166: Advanced Microprocessors AM2
Page 1 of 14
AM2: Protected-Mode Programming
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.
ECP4166: Advanced Microprocessors AM2
Page 2 of 14
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.
ECP4166: Advanced Microprocessors AM2
Page 3 of 14
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
format. For example, FBLD operates on BCD numbers, FILD operates on integers, and FLD
operates on real numbers.
ECP4166: Advanced Microprocessors AM2
Page 4 of 14
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.
ShowFPUStack: Another useful procedure, written by James Brink of Pacific Lutheran
University, displays the FPU stack. It is called with no parameters: call ShowFPUStack
ECP4166: Advanced Microprocessors AM2
Page 5 of 14
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:
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:
ECP4166: Advanced Microprocessors AM2
Page 6 of 14
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.
Exercise 1.2 Write a program that asks the user to enter the X and Y coordinates of two points on a
straight line. Calculate and display the gradient of the straight line. Use the ReadFloat and
WriteFloat procedures in 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.
ECP4166: Advanced Microprocessors AM2
Page 7 of 14
GetDateTime Gets the current date and time from the system.
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.
WriteStackFrameName Writes the current procedure’s name and stack frame to the console.
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.
ECP4166: Advanced Microprocessors AM2
Page 8 of 14
Some of the procedures that are used in this experiment are described in the following
sections.
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)
ECP4166: Advanced Microprocessors AM2
Page 9 of 14
Sample output:
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:
ECP4166: Advanced Microprocessors AM2
Page 10 of 14
BUFFER_SIZE = 5000 .data 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:
ECP4166: Advanced Microprocessors AM2
Page 11 of 14
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.
Exercise 2.2 Write a program that performs the following operations:
i. Create a new text file.
ii. Prompt the user for a student identification number, name, and date of birth.
iii. Display a graphical popup message box to ask the user if there are more data.
iv. If there are, allow the user to enter the new data and go back to step (iii).
v. Otherwise, write all data to the text file and exit the program.
References 1. Kip R. Irvine, “Assembly Language for x86 Processors”, 6
th 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/
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)
ECP4166: Advanced Microprocessors AM2
Page 12 of 14
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:
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.
ECP4166: Advanced Microprocessors AM2
Page 13 of 14
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.
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.
ECP4166: Advanced Microprocessors AM2
Page 14 of 14
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.
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.